LivePlotController.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "LivePlotController.h"
18
19#include <algorithm>
20
21#include <QThread>
22#include <QTimer>
23
25
27{
28 static constexpr int livePlotPollFrequencyHz = 30;
29
31 std::shared_ptr<armem::gui::model::MemoryViewerModel> model)
32 {
33 Logging::setTag("LivePlotController");
34 _model = std::move(model);
35 startWorker();
36 }
37
39 {
40 if (_workerThread)
41 {
42 _workerThread->quit();
43 _workerThread->wait();
44 }
45 removeAllChannels();
46 }
47
48 void
49 LivePlotController::startWorker()
50 {
51 _workerThread = new QThread();
52 _worker = new LivePlotWorker(_model, this);
53 _worker->moveToThread(_workerThread);
54
55 connect(_workerThread, &QThread::started, _worker, &LivePlotWorker::init);
56 connect(_workerThread, &QThread::finished, _worker, &LivePlotWorker::cleanup);
57 connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
58 connect(_workerThread, &QThread::finished, _workerThread, &QObject::deleteLater);
59
60 connect(this, &This::startWorkerSignal, _worker, &LivePlotWorker::onConnect);
62
63 _workerThread->start();
64 }
65
66 bool
68 {
69 return bool(_model->debugObserver());
70 }
71
72 bool
74 {
75 std::lock_guard lock(_valuesMutex);
76 return std::find(_values.begin(), _values.end(), id) != _values.end();
77 }
78
79 bool
80 LivePlotController::allPlotted(const std::vector<MemoryValueID>& ids) const
81 {
82 if (ids.empty())
83 {
84 return false;
85 }
86 std::lock_guard lock(_valuesMutex);
87 return std::all_of(ids.begin(),
88 ids.end(),
89 [this](const MemoryValueID& id)
90 { return std::find(_values.begin(), _values.end(), id) != _values.end(); });
91 }
92
93 void
94 LivePlotController::setPlotted(const std::vector<MemoryValueID>& ids, bool enable)
95 {
96 {
97 std::lock_guard lock(_valuesMutex);
98 for (const MemoryValueID& id : ids)
99 {
100 auto it = std::find(_values.begin(), _values.end(), id);
101 if (enable && it == _values.end())
102 {
103 _values.push_back(id);
104 _usedChannels.insert(
106 }
107 else if (not enable && it != _values.end())
108 {
109 _values.erase(it);
110 }
111 }
112 }
113
114 // Deactivated values are removed from the observer by the worker, which detects on its
115 // next poll that they are no longer being sent (see LivePlotWorker::removeStaleDatafields).
116 emit valuesChanged();
117 }
118
119 std::vector<LivePlotController::MemoryValueID>
121 {
122 std::lock_guard lock(_valuesMutex);
123 return _values;
124 }
125
126 void
131
132 void
137
138 void
139 LivePlotController::removeAllChannels()
140 {
141 DebugObserverInterfacePrx observer = _model->debugObserver();
142 if (not observer)
143 {
144 return;
145 }
146
147 std::set<std::string> channels;
148 {
149 std::lock_guard lock(_valuesMutex);
150 channels = std::move(_usedChannels);
151 _usedChannels.clear();
152 _values.clear();
153 }
154
155 for (const std::string& channel : channels)
156 {
157 try
158 {
159 observer->removeDebugChannel(channel);
160 }
161 catch (...)
162 {
163 // Observer may already be gone; nothing we can do.
164 }
165 }
166 }
167
168 LivePlotWorker::LivePlotWorker(std::shared_ptr<armem::gui::model::MemoryViewerModel> model,
170 _controller(controller)
171 {
172 Logging::setTag("LivePlotWorker");
173 _model = std::move(model);
174 }
175
176 void
178 {
179 // Timers must be created in their own thread, so not in the constructor.
180 _timer = new QTimer(this);
181 _timer->setInterval(1000 / livePlotPollFrequencyHz);
182 connect(_timer, &QTimer::timeout, this, &This::onPoll);
183 }
184
185 void
187 {
188 if (_timer)
189 {
190 _timer->stop();
191 _timer->deleteLater();
192 _timer = nullptr;
193 }
194 _memoryToDebugObserver.reset();
195 _debugObserverHelper.reset();
196 _lastPushed.clear();
197 }
198
199 void
201 {
202 // The DebugObserver is optional in the MemoryViewer; without it we cannot plot.
203 if (not _model->debugObserver())
204 {
205 return;
206 }
207
208 _debugObserverHelper =
209 std::make_unique<armarx::DebugObserverHelper>(_model->debugObserver(), true);
210
212 .memoryNameSystem = _model->mns(),
213 .debugObserver = *_debugObserverHelper,
214 };
215 _memoryToDebugObserver = std::make_unique<armem::client::util::MemoryToDebugObserver>(
217
218 if (_timer)
219 {
220 _timer->start();
221 }
222 }
223
224 void
226 {
227 if (_timer)
228 {
229 _timer->stop();
230 }
231 _memoryToDebugObserver.reset();
232 _debugObserverHelper.reset();
233 _lastPushed.clear();
234 }
235
236 void
237 LivePlotWorker::onPoll()
238 {
239 if (not _memoryToDebugObserver)
240 {
241 return;
242 }
243
244 std::map<std::string, std::set<std::string>> current;
245 try
246 {
247 current = _memoryToDebugObserver->pollOnce(_controller->valuesSnapshot());
248 }
249 catch (const std::exception& e)
250 {
251 // Keep _lastPushed so a transient failure does not remove still-active datafields.
253 << "Failed to send memory values to the debug observer: " << e.what();
254 return;
255 }
256 catch (...)
257 {
259 << "Failed to send memory values to the debug observer (unknown error).";
260 return;
261 }
262
263 removeStaleDatafields(current);
264 _lastPushed = std::move(current);
265 }
266
267 void
268 LivePlotWorker::removeStaleDatafields(
269 const std::map<std::string, std::set<std::string>>& current)
270 {
271 DebugObserverInterfacePrx observer = _model->debugObserver();
272 if (not observer)
273 {
274 return;
275 }
276
277 static const std::set<std::string> empty;
278 for (const auto& [channel, lastFields] : _lastPushed)
279 {
280 const auto it = current.find(channel);
281 const std::set<std::string>& currentFields = it != current.end() ? it->second : empty;
282
283 for (const std::string& field : lastFields)
284 {
285 if (currentFields.find(field) == currentFields.end())
286 {
287 try
288 {
289 observer->removeDebugDatafield(channel, field);
290 }
291 catch (...)
292 {
293 }
294 }
295 }
296
297 if (currentFields.empty())
298 {
299 try
300 {
301 observer->removeDebugChannel(channel);
302 }
303 catch (...)
304 {
305 }
306 }
307 }
308 }
309
310} // namespace armarx::armem::gui::controller
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
Definition Logging.cpp:99
void setTag(const LogTag &tag)
Definition Logging.cpp:54
static std::string makeChannelName(const armem::MemoryID &memoryID)
Streams selected memory values to the DebugObserver for live plotting.
LivePlotController(std::shared_ptr< armem::gui::model::MemoryViewerModel > model)
bool allPlotted(const std::vector< MemoryValueID > &ids) const
void setPlotted(const std::vector< MemoryValueID > &ids, bool enable)
armem::client::util::MemoryValueID MemoryValueID
std::vector< MemoryValueID > valuesSnapshot() const
Background worker that does the (blocking) memory queries and DebugObserver calls.
LivePlotWorker(std::shared_ptr< armem::gui::model::MemoryViewerModel > model, LivePlotController *controller)
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugObserverInterface > DebugObserverInterfacePrx
bool empty(const std::string &s)
Definition cxxopts.hpp:234