PageRenderTime 56ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/TaskManager.pm

https://gitlab.com/aukkwat/CZPodzone-OpenKore-Bot
Perl | 500 lines | 280 code | 54 blank | 166 comment | 62 complexity | 937bb6a17ea06b04359cbea3af2e07c0 MD5 | raw file
  1. #########################################################################
  2. # OpenKore - Task framework
  3. # Copyright (c) 2006 OpenKore Team
  4. #
  5. # This software is open source, licensed under the GNU General Public
  6. # License, version 2.
  7. # Basically, this means that you're allowed to modify and distribute
  8. # this software. However, if you distribute modified versions, you MUST
  9. # also distribute the source code.
  10. # See http://www.gnu.org/licenses/gpl.html for the full license.
  11. #########################################################################
  12. ##
  13. # MODULE DESCRIPTION: Task manager.
  14. #
  15. # Please read
  16. # <a href="http://wiki.openkore.com/index.php/AI_subsystem_and_task_framework_overview">
  17. # the AI subsystem and task framework overview
  18. # </a>
  19. # for an overview.
  20. package TaskManager;
  21. use strict;
  22. use Carp::Assert;
  23. use Modules 'register';
  24. use Task;
  25. use Utils::Set;
  26. use Utils::CallbackList;
  27. ##
  28. # TaskManager->new()
  29. #
  30. # Create a new TaskManager.
  31. sub new {
  32. my ($class) = @_;
  33. my %self = (
  34. # Set<Task>
  35. # Indexed set of currently active tasks.
  36. # Invariant:
  37. # for all $task in activeTasks:
  38. # $task->getStatus() == Task::RUNNING or Task::STOPPED
  39. # !$inactiveTasks->has($task)
  40. # if $task is not in $grayTasks:
  41. # $task owns all its mutexes.
  42. activeTasks => new Set(),
  43. # Set<Task>
  44. # Indexed set of currently inactive tasks.
  45. # Invariant:
  46. # for all $task in inactiveTasks:
  47. # $task->getStatus() == Task::INTERRUPTED, Task::INACTIVE, or Task::STOPPED
  48. # !$activeTasks->has($task)
  49. # $task owns none of its mutexes.
  50. inactiveTasks => new Set(),
  51. # Hash<String, Task>
  52. #
  53. # Currently active mutexes. The keys are the mutex names, and the
  54. # values are the tasks that have a lock on the mutex (the mutex owner).
  55. #
  56. # Invariant: all tasks in $activeMutexes appear in $activeTasks.
  57. activeMutexes => {},
  58. # Set<Task>
  59. # Indexed set of tasks for which the mutex list has changed. These tasks
  60. # must be re-scheduled.
  61. # Invariant:
  62. # for all $task in grayTasks:
  63. # $task->getStatus() == Task::RUNNING
  64. # $activeTasks->has($task)
  65. # !$inactiveTasks->has($task)
  66. grayTasks => new Set(),
  67. # Hash<String, int>
  68. # This variable remembers the number of instances for each task name.
  69. #
  70. # Invariant:
  71. # All task names in $activeTasks and $inactiveTasks are in $tasksByName.
  72. # for all $value in $tasksByName:
  73. # defined($value) && $value > 0
  74. tasksByName => {},
  75. # Maps a Task to an array of callback IDs. Used to unregister callbacks.
  76. # Invariant: Every task in $activeTasks and $inactiveTasks is in $events.
  77. events => {},
  78. # Whether tasks should be rescheduled on the
  79. # next iteration.
  80. shouldReschedule => 0,
  81. onTaskFinished => new CallbackList()
  82. );
  83. return bless \%self, $class;
  84. }
  85. ##
  86. # void $TaskManager->add(Task task)
  87. # Requires: $task->getStatus() == Task::INACTIVE
  88. #
  89. # Add a new task to this task manager.
  90. sub add {
  91. my ($self, $task) = @_;
  92. assert(defined $task) if DEBUG;
  93. assert($task->getStatus() == Task::INACTIVE) if DEBUG;
  94. $self->{inactiveTasks}->add($task);
  95. $self->{tasksByName}{$task->getName()}++;
  96. $self->{shouldReschedule} = 1;
  97. my $ID1 = $task->onMutexesChanged->add($self, \&onMutexesChanged);
  98. my $ID2 = $task->onStop->add($self, \&onStop);
  99. $self->{events}{$task} = [$ID1, $ID2];
  100. }
  101. # Reschedule tasks. Do not call this method directly!
  102. sub reschedule {
  103. my ($self) = @_;
  104. my $activeTasks = $self->{activeTasks};
  105. my $inactiveTasks = $self->{inactiveTasks};
  106. my $grayTasks = $self->{grayTasks};
  107. my $activeMutexes = $self->{activeMutexes};
  108. my $oldActiveTasks = $activeTasks->deepCopy();
  109. my $oldInactiveTasks = $inactiveTasks->deepCopy();
  110. # The algorithm produces the following result:
  111. # All active tasks do not conflict with each other, such tasks with higher
  112. # priority will be active compared to conflicting tasks with lower priority.
  113. #
  114. # This algorithm does not produce the optimal result as that would take
  115. # far too much time, but the result should be good enough in most cases.
  116. # Deactivate gray tasks that conflict with active mutexes.
  117. while (@{$grayTasks} > 0) {
  118. my $task = $grayTasks->get(0);
  119. my $hasConflict = 0;
  120. foreach my $mutex (@{$task->getMutexes()}) {
  121. if (exists $activeMutexes->{$mutex}) {
  122. $hasConflict = 1;
  123. last;
  124. }
  125. }
  126. if ($hasConflict) {
  127. # There is a conflict, so make this task inactive.
  128. $self->deactivateTask($activeTasks, $inactiveTasks,
  129. $grayTasks, $activeMutexes, $self->{tasksByName},
  130. $task);
  131. } else {
  132. # No conflict, so assign mutex locks to this task
  133. # and remove its "gray" mark.
  134. foreach my $mutex (@{$task->getMutexes()}) {
  135. $activeMutexes->{$mutex} = $task;
  136. }
  137. shift @{$grayTasks};
  138. }
  139. }
  140. # Activate inactive tasks such that active tasks don't conflict with each other.
  141. for (my $i = 0; $i < @{$inactiveTasks}; $i++) {
  142. my $task = $inactiveTasks->get($i);
  143. my @conflictingMutexes;
  144. # If this task is stopped then we just throw it away.
  145. if ($task->getStatus() == Task::STOPPED) {
  146. $inactiveTasks->remove($task);
  147. $i--;
  148. # Check whether this task conflicts with the currently locked mutexes.
  149. } elsif ((@conflictingMutexes = intersect($activeMutexes, $task->getMutexes())) == 0) {
  150. # No conflicts, we can activate this task.
  151. $activeTasks->add($task);
  152. $inactiveTasks->remove($task);
  153. $i--;
  154. foreach my $mutex (@{$task->getMutexes()}) {
  155. $activeMutexes->{$mutex} = $task;
  156. }
  157. } elsif (higherPriority($task, $activeMutexes, \@conflictingMutexes)) {
  158. # There are conflicts. Does this task have a higher priority
  159. # than all tasks specified by the conflicting mutexes?
  160. # If yes, let it steal the mutex, activate it and deactivate
  161. # the previous mutex owner.
  162. $activeTasks->add($task);
  163. $inactiveTasks->remove($task);
  164. $i--;
  165. foreach my $mutex (@{$task->getMutexes()}) {
  166. my $oldTask = $activeMutexes->{$mutex};
  167. if ($oldTask) {
  168. # Mutex was locked by lower priority task.
  169. # Deactivate old task.
  170. $self->deactivateTask($activeTasks, $inactiveTasks,
  171. $grayTasks, $activeMutexes, $self->{tasksByName},
  172. $oldTask);
  173. }
  174. $activeMutexes->{$mutex} = $task;
  175. }
  176. }
  177. }
  178. # Resume/activate newly activated tasks.
  179. foreach my $task (@{$activeTasks}) {
  180. if (!$oldActiveTasks->has($task)) {
  181. my $status = $task->getStatus();
  182. if ($status == Task::INACTIVE) {
  183. $task->activate();
  184. } elsif ($status == Task::INTERRUPTED) {
  185. $task->resume();
  186. }
  187. }
  188. }
  189. # Interrupt newly deactivated tasks.
  190. foreach my $task (@{$inactiveTasks}) {
  191. if (!$oldInactiveTasks->has($task)) {
  192. $task->interrupt();
  193. }
  194. }
  195. $self->{shouldReschedule} = 0;
  196. }
  197. ##
  198. # void $TaskManager->checkValidity()
  199. #
  200. # Check whether the internal invariants are correct. Dies if that is not the case.
  201. sub checkValidity {
  202. my ($self) = @_;
  203. my $activeTasks = $self->{activeTasks};
  204. my $inactiveTasks = $self->{inactiveTasks};
  205. my $grayTasks = $self->{grayTasks};
  206. my $activeMutexes = $self->{activeMutexes};
  207. foreach my $task (@{$activeTasks}) {
  208. assert($task->getStatus() == Task::RUNNING || $task->getStatus() == Task::STOPPED);
  209. assert(!$inactiveTasks->has($task));
  210. if (!$grayTasks->has($task)) {
  211. foreach my $mutex (@{$task->getMutexes()}) {
  212. assert($activeMutexes->{$mutex} == $task);
  213. }
  214. }
  215. }
  216. foreach my $task (@{$inactiveTasks}) {
  217. my $status = $task->getStatus();
  218. assert($status = Task::INTERRUPTED || $status == Task::INACTIVE || $status == Task::STOPPED);
  219. assert(!$activeTasks->has($task));
  220. foreach my $mutex (@{$task->getMutexes()}) {
  221. assert($activeMutexes->{$mutex} != $task);
  222. }
  223. }
  224. foreach my $task (@{$grayTasks}) {
  225. assert($activeTasks->has($task));
  226. assert(!$inactiveTasks->has($task));
  227. }
  228. my $activeMutexes = $self->{activeMutexes};
  229. foreach my $mutex (keys %{$activeMutexes}) {
  230. my $owner = $activeMutexes->{$mutex};
  231. assert($self->{activeTasks}->has($owner));
  232. }
  233. my $tasksByName = $self->{tasksByName};
  234. foreach my $value (values %{$tasksByName}) {
  235. assert(defined $value);
  236. assert($value > 0);
  237. }
  238. }
  239. ##
  240. # void $TaskManager->iterate()
  241. #
  242. # Reschedule tasks if necessary, and run one iteration of every active task.
  243. sub iterate {
  244. my ($self) = @_;
  245. $self->checkValidity() if DEBUG;
  246. $self->reschedule() if ($self->{shouldReschedule});
  247. $self->checkValidity() if DEBUG;
  248. my $activeTasks = $self->{activeTasks};
  249. my $activeMutexes = $self->{activeMutexes};
  250. for (my $i = 0; $i < @{$activeTasks}; $i++) {
  251. my $task = $activeTasks->get($i);
  252. my $status = $task->getStatus();
  253. if ($status != Task::STOPPED) {
  254. $task->iterate();
  255. $status = $task->getStatus();
  256. }
  257. # Remove tasks that are stopped or done.
  258. my $status = $task->getStatus();
  259. if ($status == Task::DONE || $status == Task::STOPPED) {
  260. $self->deactivateTask($activeTasks, $self->{inactiveTasks},
  261. $self->{grayTasks}, $activeMutexes, $self->{tasksByName},
  262. $task);
  263. # Remove the callbacks that we registered in this task.
  264. my $IDs = $self->{events}{$task};
  265. $task->onMutexesChanged->remove($IDs->[0]);
  266. $task->onStop->remove($IDs->[1]);
  267. $i--;
  268. $self->{shouldReschedule} = 1;
  269. }
  270. }
  271. $self->checkValidity() if DEBUG;
  272. }
  273. ##
  274. # void $Taskmanager->stopAll()
  275. #
  276. # Tell all tasks (whether active or inactive) to stop.
  277. sub stopAll {
  278. my ($self) = @_;
  279. foreach my $task (@{$self->{activeTasks}}, @{$self->{inactiveTasks}}) {
  280. $task->stop();
  281. if ($task->getStatus() == Task::STOPPED) {
  282. $self->{shouldReschedule} = 1;
  283. }
  284. # If the task does not stop immediately, then we'll
  285. # be notified by the onStop event once it's stopped.
  286. }
  287. }
  288. ##
  289. # int $TaskManager->countTasksByName(String name)
  290. # Ensures: result >= 0
  291. #
  292. # Count the number of tasks that have the specified name.
  293. sub countTasksByName {
  294. my ($self, $name) = @_;
  295. my $result = $self->{tasksByName}{$name};
  296. $result = 0 if (!defined $result);
  297. return $result;
  298. }
  299. ##
  300. # String $TaskManager->activeTasksString()
  301. #
  302. # Returns a string which describes the current active tasks.
  303. sub activeTasksString {
  304. my ($self) = @_;
  305. return getTaskSetString($self->{activeTasks});
  306. }
  307. ##
  308. # String $TaskManager->inactiveTasksString()
  309. #
  310. # Returns a string which describes the currently inactive tasks.
  311. sub inactiveTasksString {
  312. my ($self) = @_;
  313. return getTaskSetString($self->{inactiveTasks});
  314. }
  315. ##
  316. # String $TaskManager->activeMutexesString()
  317. #
  318. # Returns a string which describes the currently active mutexes.
  319. sub activeMutexesString {
  320. my ($self) = @_;
  321. my $activeMutexes = $self->{activeMutexes};
  322. my @entries;
  323. foreach my $mutex (keys %{$activeMutexes}) {
  324. push @entries, "$mutex (<- " . $activeMutexes->{$mutex}->getName . ")";
  325. }
  326. return join(', ', sort @entries);
  327. }
  328. sub getTaskSetString {
  329. my ($set) = @_;
  330. if (@{$set}) {
  331. my @names;
  332. foreach my $task (@{$set}) {
  333. push @names, $task->getName();
  334. }
  335. return join(', ', @names);
  336. } else {
  337. return '-';
  338. }
  339. }
  340. ##
  341. # CallbackList $TaskManager->onTaskFinished()
  342. #
  343. # This event is triggered when a task is finished, either successfully
  344. # or with an error.
  345. #
  346. # The event argument is a hash containing this item:<br>
  347. # <tt>task</tt> - The task that was finished.
  348. sub onTaskFinished {
  349. return $_[0]->{onTaskFinished};
  350. }
  351. ########## Private functions and callback handlers ##########
  352. sub onMutexesChanged {
  353. my ($self, $task) = @_;
  354. if ($task->getStatus() == Task::RUNNING) {
  355. $self->{grayTasks}->add($task);
  356. # Release its mutex locks.
  357. my $activeMutexes = $self->{activeMutexes};
  358. foreach my $mutex (keys %{$activeMutexes}) {
  359. if ($activeMutexes->{$mutex} == $task) {
  360. delete $activeMutexes->{$mutex};
  361. }
  362. }
  363. }
  364. $self->{shouldReschedule} = 1;
  365. }
  366. sub onStop {
  367. my ($self, $task) = @_;
  368. if ($self->{inactiveTasks}->has($task)) {
  369. $self->{shouldReschedule} = 1;
  370. }
  371. }
  372. # Return the intersection of the given sets.
  373. #
  374. # set1: A reference to a hash whose keys are the set elements.
  375. # set2: A reference to an array which contains the elements in the set.
  376. # Returns: An array containing the intersect elements.
  377. sub intersect {
  378. my ($set1, $set2) = @_;
  379. my @result;
  380. foreach my $element (@{$set2}) {
  381. if (exists $set1->{$element}) {
  382. push @result, $element;
  383. }
  384. }
  385. return @result;
  386. }
  387. # Check whether $task has a higher priority than all tasks specified
  388. # by the given mutexes.
  389. #
  390. # task: The task to check.
  391. # mutexTaskMapper: A hash which maps a mutex name to a task that owns that mutex.
  392. # mutexes: A list of mutexes to check.
  393. # Requires: All elements in $mutexes can be successfully mapped by $mutexTaskMapper.
  394. sub higherPriority {
  395. my ($task, $mutexTaskMapper, $mutexes) = @_;
  396. my $priority = $task->getPriority();
  397. my $result = 1;
  398. for (my $i = 0; $i < @{$mutexes} && $result; $i++) {
  399. my $task2 = $mutexTaskMapper->{$mutexes->[$i]};
  400. $result = $result && $priority > $task2->getPriority();
  401. }
  402. return $result;
  403. }
  404. # Deactivate an active task by removing it from the active task list
  405. # and the gray list, and removing its mutex locks. If the task isn't
  406. # completed or stopped, then it will be added to the inactive task list.
  407. sub deactivateTask {
  408. my ($self, $activeTasks, $inactiveTasks, $grayTasks, $activeMutexes, $tasksByName, $task) = @_;
  409. my $status = $task->getStatus();
  410. if ($status != Task::DONE && $status != Task::STOPPED) {
  411. $inactiveTasks->add($task);
  412. } else {
  413. my $name = $task->getName();
  414. $tasksByName->{$name}--;
  415. assert($tasksByName->{$name} >= 0) if DEBUG;
  416. if ($tasksByName->{$name} == 0) {
  417. delete $tasksByName->{$name};
  418. }
  419. $self->{onTaskFinished}->call($self, { task => $task });
  420. }
  421. $activeTasks->remove($task);
  422. $grayTasks->remove($task);
  423. foreach my $mutex (@{$task->getMutexes()}) {
  424. if ($activeMutexes->{$mutex} == $task) {
  425. delete $activeMutexes->{$mutex};
  426. }
  427. }
  428. }
  429. # sub printTaskSet {
  430. # my ($set, $name) = @_;
  431. # my @names;
  432. # foreach my $task (@{$set}) {
  433. # push @names, $task->getName();
  434. # }
  435. # print "$name = " . join(',', @names) . "\n";
  436. # }
  437. #
  438. # sub printActiveMutexes {
  439. # my ($activeMutexes) = @_;
  440. # my @entries;
  441. # foreach my $mutex (keys %{$activeMutexes}) {
  442. # push @entries, "$mutex (owned by " . $activeMutexes->{$mutex}->getName . ")";
  443. # }
  444. # print "Active mutexes: " . join(', ', @entries) . "\n";
  445. # }
  446. 1;