Scheduler.cpp
Go to the documentation of this file.
1#include "Scheduler.h"
2
3#include <map>
4#include <mutex>
5#include <optional>
6#include <string>
7#include <vector>
8
9#include <algorithm>
10
11#include <SimoxUtility/color/Color.h>
12#include <VirtualRobot/XML/RobotIO.h>
13
21
23
26
31
33{
34
35 Scheduler::Scheduler(const InjectedServices& srv, const Params& params) :
36 srv(srv), params(params), reachedMonitor(params.reached)
37 {
38 init();
39 }
40
41 void
42 Scheduler::init()
43 {
44 robot_ = srv.virtualRobotReader->getRobotWaiting(
45 params.robotName,
47 VirtualRobot::RobotIO::RobotDescription::eStructure);
48 ARMARX_CHECK_NOT_NULL(robot_) << params.robotName;
49
50 auto const infinite = armarx::Duration::MilliSeconds(-1);
51 bool constexpr keepInQueue = true;
52
53 _idleGazeTarget.name = "idle";
54 _idleGazeTarget.position = FramedPosition(
55 params.defaultTarget, robot_->getRootNode()->getName(), robot_->getName());
56 _idleGazeTarget.priority =
58 _idleGazeTarget.duration = infinite;
59 _idleGazeTarget.keepInQueue = keepInQueue;
60 _idleGazeTarget.creationTimestamp = DateTime::Now();
61 _idleGazeTarget.activationTimestamp = DateTime::Now();
62
63 srv.controllerHandler->createController();
64 resetRequestedTargets();
65
67 srv.controllerHandler->activateController();
68
69 if (task and task->isRunning())
70 {
71 task->stop();
72 }
73
74 task = new armarx::SimplePeriodicTask<>([&]() { scheduleNextTarget(); }, 100);
75 task->start();
76
77 if (visualizationTask and visualizationTask->isRunning())
78 {
79 visualizationTask->stop();
80 }
81
82 visualizationTask =
83 new armarx::SimplePeriodicTask<>([&]() { visualizeActiveTarget(); }, 100);
84 visualizationTask->start();
85 }
86
87 void
89 {
90 _clearGazeTargets.store(true);
91 }
92
93 void
94 Scheduler::scheduleNextTarget()
95 {
96 bool constexpr debugQueue = false;
97
98 auto debug = [&](std::string const& step)
99 {
100 if (not debugQueue)
101 {
102 return;
103 }
104
105 int i = 0;
106 ARMARX_INFO << "Gaze target queue contents (" << step << "): ";
107 for (auto const& targetInQueue : requestedTargets)
108 {
109 ARMARX_INFO << " #" << i++ << " " << targetInQueue;
110 }
111 };
112
113 if (requestedTargets.size() == 0)
114 {
115 ARMARX_WARNING << "No gaze targets in queue. At least the idle gaze target should be "
116 "in the queue.";
117 return;
118 }
119
120 // Get new gaze targets and copy them into this thread.
121 std::vector<gaze_targets::GazeTarget> newGazeTargets = [&]
122 {
123 std::scoped_lock<std::mutex> targetLock(_newGazeTargetsMutex);
124 std::vector<gaze_targets::GazeTarget> copy = _newGazeTargets;
125 _newGazeTargets.clear();
126 return copy;
127 }();
128
129 // Get the reference timestamp for this scheduling step.
130 DateTime const scheduleStep = Clock::Now();
131
132 debug("before filter");
133
134 // First, filter all requested targets that have expired. Consider all expired (except for
135 // idle gaze target) if clearing all gaze targets was requested.
136 if (_clearGazeTargets.load())
137 {
138 resetRequestedTargets();
139 _clearGazeTargets.store(false);
140 }
141 else
142 {
143 RequestedTargets newRequestedTargets;
144
145 for (auto const& targetInQueue : requestedTargets)
146 {
147 if (not targetInQueue.isExpired(scheduleStep))
148 {
149 newRequestedTargets.emplace(targetInQueue);
150 }
151 else
152 {
153 ARMARX_INFO << "Gaze target " << targetInQueue.name << " expired.";
154 leaveActiveSlot(targetInQueue, "duration elapsed", false);
155 }
156 }
157
158 requestedTargets = newRequestedTargets;
159 }
160
161 debug("after filter, before insertion");
162
163 // Second, add new gaze targets, replace task level targets or existing ones with the same
164 // name if applicable.
165 for (auto const& target : newGazeTargets)
166 {
167 RequestedTargets newRequestedTargets;
168 newRequestedTargets.emplace(target);
169
170 for (auto const& targetInQueue : requestedTargets)
171 {
172 if (targetInQueue.name == target.name)
173 {
174 // Do not keep targets with the same name in the queue, since they should be
175 // overwritten with the new one.
176 }
177 else if (target.priority.attentionType ==
179 targetInQueue.priority.attentionType ==
181 not targetInQueue.keepInQueue)
182 {
183 // New target is task driven, and this target is too and should not be kept in
184 // the queue.
185 }
186 else
187 {
188 // Keep in queue.
189 newRequestedTargets.emplace(targetInQueue);
190 }
191 }
192
193 requestedTargets = newRequestedTargets;
194 }
195
196 debug("after insertion, before selection");
197
198 // Third, use the first gaze target as next gaze target. It should be the first in the
199 // queue.
200 std::optional<gaze_targets::GazeTarget> const maybeNextTarget =
201 [&]() -> std::optional<gaze_targets::GazeTarget>
202 {
203 if (requestedTargets.empty())
204 {
205 return std::nullopt;
206 }
207
208 return *requestedTargets.begin();
209 }();
210
211 if (not maybeNextTarget.has_value())
212 {
213 ARMARX_WARNING << "Could not find any gaze targets after filtering. At least the idle "
214 "gaze target was expected. Trying to recover by resetting gaze "
215 "targets queue ...";
216 _clearGazeTargets.store(true); // Try to reset gaze targets list to recover.
217 return;
218 }
219
220 auto const currentTarget = currentTargetBuffer.getUpToDateReadBuffer();
221 gaze_targets::GazeTarget const nextTarget = maybeNextTarget.value();
222
223 if (not currentTarget.has_value() or currentTarget->isExpired() or
224 nextTarget != currentTarget)
225 {
226 if (currentTarget.has_value() and currentTarget->name != nextTarget.name)
227 {
228 const bool staysQueued =
229 currentTarget->keepInQueue and
230 std::any_of(requestedTargets.begin(),
231 requestedTargets.end(),
232 [&](const gaze_targets::GazeTarget& queued)
233 { return queued.name == currentTarget->name; });
234
235 leaveActiveSlot(*currentTarget, "preempted by " + nextTarget.name, staysQueued);
236 }
237
238 submitControlTarget(nextTarget);
239 }
240 }
241
242 void
243 Scheduler::submitControlTarget(gaze_targets::GazeTarget const& target)
244 {
245 ARMARX_INFO << "Scheduling " << target << " now.";
246
247 ++currentTargetId;
248 reachedMonitor.reset(currentTargetId);
249 reachedEmitted = false;
250 unreachableEmitted = false;
251
252 currentTargetBuffer.getWriteBuffer() = target;
253 currentTargetBuffer.commitWrite();
254 srv.controllerHandler->updateControllerTarget(target, currentTargetId);
255
256 if (const std::optional<armem::MemoryID> gazeTargetID = gazeTargetIDOf(target.name))
257 {
258 srv.statusPublisher->scheduled(*gazeTargetID);
259 }
260 }
261
262 std::optional<armem::MemoryID>
263 Scheduler::gazeTargetIDOf(const std::string& targetName) const
264 {
265 const auto it = gazeTargetIDs.find(targetName);
266
267 if (it == gazeTargetIDs.end())
268 {
269 return std::nullopt;
270 }
271
272 return it->second;
273 }
274
275 void
276 Scheduler::leaveActiveSlot(const gaze_targets::GazeTarget& target,
277 const std::string& reason,
278 const bool staysQueued)
279 {
280 const std::optional<armem::MemoryID> gazeTargetID = gazeTargetIDOf(target.name);
281
282 if (not gazeTargetID.has_value())
283 {
284 return;
285 }
286
287 if (staysQueued)
288 {
289 srv.statusPublisher->preempted(*gazeTargetID, reason);
290 return;
291 }
292
293 // Terminal: whether the requester got what it asked for is decided by whether the gaze ever
294 // settled on this target, not by how it ended.
295 if (srv.statusPublisher->wasReached(*gazeTargetID))
296 {
297 srv.statusPublisher->released(*gazeTargetID, reason);
298 }
299 else
300 {
301 srv.statusPublisher->aborted(*gazeTargetID, reason);
302 }
303
304 gazeTargetIDs.erase(target.name);
305 }
306
307 void
309 {
310 const gaze_controller::GazeResidual residual{.angularError = status.angularError,
311 .lateralError = status.lateralError};
312
313 reachedMonitor.update(status.targetId, residual, status.targetReachable);
314
315 const auto currentTarget = currentTargetBuffer.getUpToDateReadBuffer();
316
317 if (not currentTarget.has_value())
318 {
319 return;
320 }
321
322 const std::optional<armem::MemoryID> gazeTargetID = gazeTargetIDOf(currentTarget->name);
323
324 if (not gazeTargetID.has_value())
325 {
326 return;
327 }
328
329 if (not reachedEmitted and reachedMonitor.reached())
330 {
331 reachedEmitted = true;
332
333 // ARMARX_INFO on purpose: this is the first of the three hops the combined log shows
334 // for a transition (detect here, commit in the publisher, receive in the client).
335 ARMARX_INFO << "Gaze target " << QUOTED(currentTarget->name)
336 << " reached (angular error " << reachedMonitor.residual().angularError
337 << " rad, lateral error " << reachedMonitor.residual().lateralError
338 << " mm); controller reported at " << status.timestamp.timeSinceEpoch
339 << ", handled at " << armarx::Clock::Now() << ".";
340
341 srv.statusPublisher->reached(*gazeTargetID, reachedMonitor.residual());
342 }
343
344 if (not unreachableEmitted and not reachedEmitted and reachedMonitor.unreachable())
345 {
346 unreachableEmitted = true;
347
348 ARMARX_INFO << "Gaze target " << QUOTED(currentTarget->name)
349 << " cannot be reached by the controller; aborting it.";
350
351 srv.statusPublisher->aborted(*gazeTargetID,
352 "unreachable: the controller cannot look there");
353 gazeTargetIDs.erase(currentTarget->name);
354 }
355 }
356
357 void
358 Scheduler::resetRequestedTargets()
359 {
360 ARMARX_INFO << "Resetting gaze targets queue, old requests are discarded.";
361
362 for (const auto& targetInQueue : requestedTargets)
363 {
364 leaveActiveSlot(targetInQueue, "queue reset", false);
365 }
366
367 requestedTargets.clear();
368
369 ARMARX_INFO << "Adding idle target `" << params.defaultTarget.transpose() << "`.";
370
371 currentTargetBuffer.getWriteBuffer() = std::nullopt;
372 currentTargetBuffer.commitWrite();
373
374 requestedTargets.emplace(_idleGazeTarget);
375 }
376
377 void
378 Scheduler::visualizeActiveTarget()
379 {
382 {
383 return;
384 }
385
386 std::string const activeTargetLayerName = "active_target";
387
388 auto const currentTarget = currentTargetBuffer.getUpToDateReadBuffer();
389
391
392 bool visualize = currentTarget.has_value();
393
394 if (currentTarget.has_value())
395 {
396 switch (currentTarget->priority.attentionType)
397 {
399 visualize = params.visualizeTaskDrivenGazeTarget;
400 break;
402 visualize = params.visualizeStimulusDrivenTarget;
403 break;
405 visualize = params.visualizeRandomEventTarget;
406 break;
407 }
408 }
409
410 // Do not visualize if current gaze target has no value or if current gaze target is an
411 // random event but only stimulus- and task-driven gaze targets should be visualized.
412 if (not visualize)
413 {
414 srv.arviz->commitDeleteLayer(activeTargetLayerName);
415 return;
416 }
417
418 if (not srv.virtualRobotReader->synchronizeRobot(*robot_, armarx::Clock::Now()))
419 {
420 ARMARX_VERBOSE << "Failed to synchronize robot. Cannot visualize targets.";
421 return;
422 }
423
424 Eigen::Vector3f const globalPosition = currentTarget->position.toGlobalEigen(robot_);
425
426 std::map<gaze_targets::AttentionType, simox::Color> attentionTypeColor{
428 simox::Color::gray(128, params.targetVizAlpha)},
430 simox::Color::orange(255, params.targetVizAlpha)},
432 simox::Color::red(255, params.targetVizAlpha)}};
433
434 auto const color = attentionTypeColor.at(currentTarget->priority.attentionType);
435 Eigen::Vector3f const from =
436 robot_->getRobotNode(params.gazeOriginFrameName)->getGlobalPosition();
437 Eigen::Vector3f const direction_vec = (globalPosition - from).normalized();
438 Eigen::Vector3f const to = (globalPosition - from).norm() > 750
439 ? Eigen::Vector3f(from + (direction_vec * 700))
440 : Eigen::Vector3f(globalPosition - (direction_vec * 50));
441
442 auto l = srv.arviz->layer(activeTargetLayerName);
443 l.add(armarx::viz::Sphere("target").position(globalPosition).radius(25).color(color));
444 l.add(armarx::viz::Arrow("gaze_direction")
445 .fromTo(from + (direction_vec * 50), to)
446 .width(10)
447 .color(color));
448 srv.arviz->commit(l);
449 }
450
451 void
453 {
454 {
455 std::scoped_lock<std::mutex> targetLock(_newGazeTargetsMutex);
456 target.activationTimestamp = armarx::Clock::Now(); // TODO: This is a hack for now.
457 _newGazeTargets.push_back(target);
458 }
459
460 if (not gazeTargetID.hasEntityName())
461 {
462 // The idle target: nobody requested it, so there is nobody to report to.
463 return;
464 }
465
466 gazeTargetIDs[target.name] = gazeTargetID;
467
468 // Tells a waiting client the scheduler has seen its request -- which distinguishes "queued
469 // behind something" from "the scheduler is not running".
470 srv.statusPublisher->requested(gazeTargetID, target.creationTimestamp);
471 }
472
474 {
475
476 if (task->isRunning())
477 {
478 ARMARX_INFO << "Stopping task";
479 task->stop();
480 }
481
482 if (visualizationTask->isRunning())
483 {
484 ARMARX_INFO << "Stopping visualization task";
485 visualizationTask->stop();
486 }
487 }
488} // namespace armarx::view_selection::gaze_scheduler
#define QUOTED(x)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static void WaitFor(const Duration &duration)
Wait for a certain duration on the virtual clock.
Definition Clock.cpp:99
static DateTime Now()
Definition DateTime.cpp:51
static DateTime Invalid()
Definition DateTime.cpp:57
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
The FramedPosition class.
Definition FramedPose.h:158
void start()
Starts the thread.
void stop()
Stops the thread.
bool isRunning() const
Retrieve running state of the thread.
bool hasEntityName() const
Definition MemoryID.h:121
void handleControllerStatus(const gaze_controller::GazeControllerStatus &status)
Feed one report from the low-level controller.
void submitToQueue(gaze_targets::GazeTarget target, armem::MemoryID gazeTargetID)
Queue a request.
Scheduler(const InjectedServices &srv, const Params &params)
Definition Scheduler.cpp:35
Business Object (BO) class of GazeTarget.
Definition GazeTarget.h:22
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
@ RandomEvent
Random Targets with lowest priority.
@ StimulusDriven
Stimulus-Driven attention is executed when there is no Task-Driven GazeTarget.
@ TaskDriven
Task-Driven attention has highest priority.
state::Type from(Eigen::Vector3f targetPosition)
SimplePeriodicTask(Ts...) -> SimplePeriodicTask< std::function< void(void)> >
Vertex target(const detail::edge_base< Directed, Vertex > &e, const PCG &)
double norm(const Point &a)
Definition point.hpp:102
Periodic report of the low-level gaze controller's residual.
How far the gaze currently is from a target.
std::experimental::observer_ptr< viz::Client > arviz
Definition Scheduler.h:44
std::experimental::observer_ptr< ControllerHandlerInterface > controllerHandler
Definition Scheduler.h:46
std::experimental::observer_ptr< armem::robot_state::VirtualRobotReader > virtualRobotReader
Definition Scheduler.h:43