GazeTargetWaiter.cpp
Go to the documentation of this file.
1#include "GazeTargetWaiter.h"
2
3#include <chrono>
4#include <mutex>
5#include <string>
6
11
14
16{
17
18 namespace
19 {
20 /// How often the abort predicate is re-checked while blocked.
21 constexpr auto abortPollInterval = std::chrono::milliseconds(50);
22 } // namespace
23
24 GazeTargetWaiter::Result::operator bool() const
25 {
26 return status.wasReached();
27 }
28
30 {
31 handler.onAny([this](const gaze_targets::GazeTargetStatus& status)
32 { handleStatus(status); });
33 }
34
35 void
36 GazeTargetWaiter::expect(const std::string& targetName)
37 {
38 const std::scoped_lock lock(mutex);
39
40 // A fresh expectation: anything left from an earlier run of the same target is stale.
41 expectations[targetName] = Expectation{};
42 }
43
44 void
45 GazeTargetWaiter::bind(const std::string& targetName, const armem::MemoryID& gazeTargetID)
46 {
47 const std::scoped_lock lock(mutex);
48
49 const auto it = expectations.find(targetName);
50
51 if (it == expectations.end())
52 {
53 return;
54 }
55
56 it->second.gazeTargetID = gazeTargetID.getEntitySnapshotID();
57
58 // An update may already have arrived under the name alone. If it was about a different
59 // request, it is not ours after all.
60 if (it->second.status.gazeTargetID.hasEntityName() and
61 it->second.status.gazeTargetID.getEntitySnapshotID() != *it->second.gazeTargetID)
62 {
63 it->second.status = gaze_targets::GazeTargetStatus{};
64 it->second.settled = false;
65 }
66 }
67
68 void
69 GazeTargetWaiter::forget(const std::string& targetName)
70 {
71 const std::scoped_lock lock(mutex);
72
73 expectations.erase(targetName);
74 }
75
76 void
77 GazeTargetWaiter::handleStatus(const gaze_targets::GazeTargetStatus& status)
78 {
79 {
80 const std::scoped_lock lock(mutex);
81
82 const auto it = expectations.find(status.gazeTargetID.entityName);
83
84 if (it == expectations.end())
85 {
86 return;
87 }
88
89 // Once bound, only the exact request counts. This is what keeps two targets that are
90 // in flight under the same name apart.
91 if (it->second.gazeTargetID.has_value() and
92 status.gazeTargetID.getEntitySnapshotID() != *it->second.gazeTargetID)
93 {
94 return;
95 }
96
97 it->second.status = status;
98 it->second.settled = status.wasReached() or status.isTerminal();
99 }
100
101 condition.notify_all();
102 }
103
105 GazeTargetWaiter::wait(const std::string& targetName,
106 const armarx::Duration& timeout,
107 const std::function<bool()>& shouldAbort)
108 {
109 // A non-positive timeout means "no deadline", as it does for a gaze target's own duration.
110 const armarx::DateTime deadline =
111 timeout.isPositive() ? armarx::Clock::Now() + timeout : armarx::DateTime::Invalid();
112 const armarx::DateTime start = armarx::Clock::Now();
113
114 if (deadline.isValid())
115 {
116 ARMARX_INFO << "Waiting until gaze target " << QUOTED(targetName)
117 << " is reached (timeout " << timeout.toMilliSecondsDouble() << " ms).";
118 }
119 else
120 {
121 ARMARX_INFO << "Waiting until gaze target " << QUOTED(targetName)
122 << " is reached (no timeout).";
123 }
124
125 Result result;
126
127 {
128 std::unique_lock lock(mutex);
129
130 const auto it = expectations.find(targetName);
131
132 if (it == expectations.end())
133 {
134 ARMARX_WARNING << "No expectation armed for gaze target " << QUOTED(targetName)
135 << ". Did you forget to call expect() before committing it?";
136 return Result{.status = {}, .message = "not armed"};
137 }
138
139 while (not it->second.settled)
140 {
141 if (shouldAbort and shouldAbort())
142 {
143 result.message = "aborted by caller";
144 break;
145 }
146
147 if (deadline.isValid() and armarx::Clock::Now() >= deadline)
148 {
149 result.message = "wait timeout expired";
150 break;
151 }
152
153 condition.wait_for(lock, abortPollInterval);
154 }
155
156 result.status = it->second.status;
157 expectations.erase(it);
158 }
159
160 const armarx::Duration elapsed = armarx::Clock::Now() - start;
161
162 ARMARX_INFO << "Finished waiting for gaze target " << QUOTED(targetName) << " after "
163 << elapsed.toMilliSecondsDouble() << " ms: "
165 << (result.message.empty() ? std::string() : " (" + result.message + ")")
166 << ", reached: " << (result ? "yes" : "no") << ".";
167
168 return result;
169 }
170
171} // namespace armarx::view_selection::client
#define QUOTED(x)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static DateTime Invalid()
Definition DateTime.cpp:57
MemoryID getEntitySnapshotID() const
Definition MemoryID.cpp:318
Represents a point in time.
Definition DateTime.h:25
Represents a duration.
Definition Duration.h:17
double toMilliSecondsDouble() const
Returns the amount of milliseconds.
Definition Duration.cpp:66
bool isPositive() const
Tests whether the duration is positive (value in µs > 0).
Definition Duration.cpp:168
void expect(const std::string &targetName)
Start collecting updates for this target. Call before committing it.
void bind(const std::string &targetName, const armem::MemoryID &gazeTargetID)
Narrow the expectation to the exact request snapshot, once the commit returned its ID.
GazeTargetWaiter(StatusHandlerInterface &handler)
Result wait(const std::string &targetName, const armarx::Duration &timeout, const std::function< bool()> &shouldAbort)
Block until the target is reached, ends, the abort predicate fires or the timeout expires.
void forget(const std::string &targetName)
Drop an expectation without waiting for it.
Registration of callbacks for gaze target lifecycle updates.
virtual void onAny(const Callback &callback)=0
Called for every update, whatever its status.
Business Object (BO) class of GazeTargetStatus: the lifecycle of one gaze target request,...
#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
const simox::meta::EnumNames< TargetStatus > TargetStatusNames
std::string message
Why the wait ended, if it did not end on a status update.