##// END OF EJS Templates
Made Variable data update atomic ease thread safety and avoid mixing...
jeandet -
r16:5da3a19e8770
parent child
Show More
@@ -1,507 +1,510
1 1 #ifndef SCIQLOP_DATASERIES_H
2 2 #define SCIQLOP_DATASERIES_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Common/SortUtils.h>
7 7
8 8 #include <Data/ArrayData.h>
9 9 #include <Data/DataSeriesMergeHelper.h>
10 10 #include <Data/IDataSeries.h>
11 11 #include <Data/OptionalAxis.h>
12 12
13 13 #include <QLoggingCategory>
14 14 #include <QReadLocker>
15 15 #include <QReadWriteLock>
16 16 #include <memory>
17 17
18 18 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
19 19 // definitions with inheritance. Inline method is used instead
20 20 inline const QLoggingCategory &LOG_DataSeries()
21 21 {
22 22 static const QLoggingCategory category{"DataSeries"};
23 23 return category;
24 24 }
25 25
26 26 template <int Dim>
27 27 class DataSeries;
28 28
29 29 namespace dataseries_detail {
30 30
31 31 template <int Dim, bool IsConst>
32 32 class IteratorValue : public DataSeriesIteratorValue::Impl {
33 33 public:
34 34 friend class DataSeries<Dim>;
35 35
36 36 template <bool IC = IsConst, typename = std::enable_if_t<IC == false> >
37 37 explicit IteratorValue(DataSeries<Dim> &dataSeries, bool begin)
38 38 : m_XIt(begin ? dataSeries.xAxisData()->begin() : dataSeries.xAxisData()->end()),
39 39 m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end()),
40 40 m_YItBegin{dataSeries.yAxis().begin()},
41 41 m_YItEnd{dataSeries.yAxis().end()}
42 42 {
43 43 }
44 44
45 45 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
46 46 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
47 47 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
48 48 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
49 49 : dataSeries.valuesData()->cend()),
50 50 m_YItBegin{dataSeries.yAxis().cbegin()},
51 51 m_YItEnd{dataSeries.yAxis().cend()}
52 52 {
53 53 }
54 54
55 55 IteratorValue(const IteratorValue &other) = default;
56 56
57 57 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
58 58 {
59 59 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
60 60 }
61 61
62 62 int distance(const DataSeriesIteratorValue::Impl &other) const override try {
63 63 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
64 64 return m_XIt->distance(*otherImpl.m_XIt);
65 65 }
66 66 catch (const std::bad_cast &) {
67 67 return 0;
68 68 }
69 69
70 70 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
71 71 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
72 72 return std::tie(m_XIt, m_ValuesIt, m_YItBegin, m_YItEnd)
73 73 == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt, otherImpl.m_YItBegin,
74 74 otherImpl.m_YItEnd);
75 75 }
76 76 catch (const std::bad_cast &) {
77 77 return false;
78 78 }
79 79
80 80 bool lowerThan(const DataSeriesIteratorValue::Impl &other) const override try {
81 81 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
82 82 return m_XIt->lowerThan(*otherImpl.m_XIt);
83 83 }
84 84 catch (const std::bad_cast &) {
85 85 return false;
86 86 }
87 87
88 88 std::unique_ptr<DataSeriesIteratorValue::Impl> advance(int offset) const override
89 89 {
90 90 auto result = clone();
91 91 result->next(offset);
92 92 return result;
93 93 }
94 94
95 95 void next(int offset) override
96 96 {
97 97 m_XIt->next(offset);
98 98 m_ValuesIt->next(offset);
99 99 }
100 100
101 101 void prev() override
102 102 {
103 103 --m_XIt;
104 104 --m_ValuesIt;
105 105 }
106 106
107 107 double x() const override { return m_XIt->at(0); }
108 108 std::vector<double> y() const override
109 109 {
110 110 std::vector<double> result{};
111 111 std::transform(m_YItBegin, m_YItEnd, std::back_inserter(result),
112 112 [](const auto &it) { return it.first(); });
113 113
114 114 return result;
115 115 }
116 116
117 117 double value() const override { return m_ValuesIt->at(0); }
118 118 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
119 119 double minValue() const override { return m_ValuesIt->min(); }
120 120 double maxValue() const override { return m_ValuesIt->max(); }
121 121 QVector<double> values() const override { return m_ValuesIt->values(); }
122 122
123 123 void swap(DataSeriesIteratorValue::Impl &other) override
124 124 {
125 125 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
126 126 m_XIt->impl()->swap(*otherImpl.m_XIt->impl());
127 127 m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl());
128 128 m_YItBegin->impl()->swap(*otherImpl.m_YItBegin->impl());
129 129 m_YItEnd->impl()->swap(*otherImpl.m_YItEnd->impl());
130 130 }
131 131
132 132 private:
133 133 ArrayDataIterator m_XIt;
134 134 ArrayDataIterator m_ValuesIt;
135 135 ArrayDataIterator m_YItBegin;
136 136 ArrayDataIterator m_YItEnd;
137 137 };
138 138 } // namespace dataseries_detail
139 139
140 140 /**
141 141 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
142 142 *
143 143 * The DataSeries represents values on one or two axes, according to these rules:
144 144 * - the x-axis is always defined
145 145 * - an y-axis can be defined or not. If set, additional consistency checks apply to the values (see
146 146 * below)
147 147 * - the values are defined on one or two dimensions. In the case of 2-dim values, the data is
148 148 * distributed into components (for example, a vector defines three components)
149 149 * - New values can be added to the series, on the x-axis.
150 150 * - Once initialized to the series creation, the y-axis (if defined) is no longer modifiable
151 151 * - Data representing values and axes are associated with a unit
152 152 * - The data series is always sorted in ascending order on the x-axis.
153 153 *
154 154 * Consistency checks are carried out between the axes and the values. These controls are provided
155 155 * throughout the DataSeries lifecycle:
156 156 * - the number of data on the x-axis must be equal to the number of values (in the case of
157 157 * 2-dim ArrayData for values, the test is performed on the number of values per component)
158 158 * - if the y-axis is defined, the number of components of the ArrayData for values must equal the
159 159 * number of data on the y-axis.
160 160 *
161 161 * Examples:
162 162 * 1)
163 163 * - x-axis: [1 ; 2 ; 3]
164 164 * - y-axis: not defined
165 165 * - values: [10 ; 20 ; 30] (1-dim ArrayData)
166 166 * => the DataSeries is valid, as x-axis and values have the same number of data
167 167 *
168 168 * 2)
169 169 * - x-axis: [1 ; 2 ; 3]
170 170 * - y-axis: not defined
171 171 * - values: [10 ; 20 ; 30 ; 40] (1-dim ArrayData)
172 172 * => the DataSeries is invalid, as x-axis and values haven't the same number of data
173 173 *
174 174 * 3)
175 175 * - x-axis: [1 ; 2 ; 3]
176 176 * - y-axis: not defined
177 177 * - values: [10 ; 20 ; 30
178 178 * 40 ; 50 ; 60] (2-dim ArrayData)
179 179 * => the DataSeries is valid, as x-axis has 3 data and values contains 2 components with 3
180 180 * data each
181 181 *
182 182 * 4)
183 183 * - x-axis: [1 ; 2 ; 3]
184 184 * - y-axis: [1 ; 2]
185 185 * - values: [10 ; 20 ; 30
186 186 * 40 ; 50 ; 60] (2-dim ArrayData)
187 187 * => the DataSeries is valid, as:
188 188 * - x-axis has 3 data and values contains 2 components with 3 data each AND
189 189 * - y-axis has 2 data and values contains 2 components
190 190 *
191 191 * 5)
192 192 * - x-axis: [1 ; 2 ; 3]
193 193 * - y-axis: [1 ; 2 ; 3]
194 194 * - values: [10 ; 20 ; 30
195 195 * 40 ; 50 ; 60] (2-dim ArrayData)
196 196 * => the DataSeries is invalid, as:
197 197 * - x-axis has 3 data and values contains 2 components with 3 data each BUT
198 198 * - y-axis has 3 data and values contains only 2 components
199 199 *
200 200 * @tparam Dim The dimension of the values data
201 201 *
202 202 */
203 203 template <int Dim>
204 204 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
205 205 friend class DataSeriesMergeHelper;
206 206
207 207 public:
208 208 /// @sa IDataSeries::xAxisData()
209 209 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
210 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
210 const std::shared_ptr<ArrayData<1> > xAxisData() const override { return m_XAxisData; }
211 211
212 212 /// @sa IDataSeries::xAxisUnit()
213 213 Unit xAxisUnit() const override { return m_XAxisUnit; }
214 214
215 215 /// @sa IDataSeries::yAxisUnit()
216 216 Unit yAxisUnit() const override { return m_YAxis.unit(); }
217 217
218 218 /// @return the values dataset
219 219 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
220 220 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
221 221
222 222 /// @sa IDataSeries::valuesUnit()
223 223 Unit valuesUnit() const override { return m_ValuesUnit; }
224 224
225 225 int nbPoints() const override { return m_ValuesData->totalSize(); }
226 226
227 227 std::pair<double, double> yBounds() const override { return m_YAxis.bounds(); }
228 228
229 229 void clear()
230 230 {
231 231 m_XAxisData->clear();
232 232 m_ValuesData->clear();
233 233 }
234 234
235 235 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
236 236
237 237 /// Merges into the data series an other data series.
238 238 ///
239 239 /// The two dataseries:
240 240 /// - must be of the same dimension
241 241 /// - must have the same y-axis (if defined)
242 242 ///
243 243 /// If the prerequisites are not valid, the method does nothing
244 244 ///
245 245 /// @remarks the data series to merge with is cleared after the operation
246 246 void merge(IDataSeries *dataSeries) override
247 247 {
248 248 dataSeries->lockWrite();
249 249 lockWrite();
250 250
251 251 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
252 252 if (m_YAxis == other->m_YAxis) {
253 253 DataSeriesMergeHelper::merge(*other, *this);
254 254 }
255 255 else {
256 256 qCWarning(LOG_DataSeries())
257 257 << QObject::tr("Can't merge data series that have not the same y-axis");
258 258 }
259 259 }
260 260 else {
261 261 qCWarning(LOG_DataSeries())
262 262 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
263 263 }
264 264 unlock();
265 265 dataSeries->unlock();
266 266 }
267 267
268 268 void purge(double min, double max) override
269 269 {
270 270 // Nothing to purge if series is empty
271 271 if (isEmpty()) {
272 272 return;
273 273 }
274 274
275 275 if (min > max) {
276 276 std::swap(min, max);
277 277 }
278 278
279 279 // Nothing to purge if series min/max are inside purge range
280 280 auto xMin = cbegin()->x();
281 281 auto xMax = (--cend())->x();
282 282 if (xMin >= min && xMax <= max) {
283 283 return;
284 284 }
285 285
286 286 auto lowerIt = std::lower_bound(
287 287 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
288 288 erase(begin(), lowerIt);
289 289 auto upperIt = std::upper_bound(
290 290 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
291 291 erase(upperIt, end());
292 292 }
293 293
294 294 // ///////// //
295 295 // Iterators //
296 296 // ///////// //
297 297
298 298 DataSeriesIterator begin() override
299 299 {
300 300 return DataSeriesIterator{DataSeriesIteratorValue{
301 301 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
302 302 }
303 303
304 304 DataSeriesIterator end() override
305 305 {
306 306 return DataSeriesIterator{DataSeriesIteratorValue{
307 307 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
308 308 }
309 309
310 310 DataSeriesIterator cbegin() const override
311 311 {
312 312 return DataSeriesIterator{DataSeriesIteratorValue{
313 313 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
314 314 }
315 315
316 316 DataSeriesIterator cend() const override
317 317 {
318 318 return DataSeriesIterator{DataSeriesIteratorValue{
319 319 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
320 320 }
321 321
322 322 void erase(DataSeriesIterator first, DataSeriesIterator last)
323 323 {
324 324 auto firstImpl
325 325 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
326 326 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
327 327
328 328 if (firstImpl && lastImpl) {
329 329 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
330 330 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
331 331 }
332 332 }
333 333
334 334 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
335 335 {
336 336 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
337 337 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
338 338
339 339 if (firstImpl && lastImpl) {
340 340 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
341 341 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
342 342 }
343 343 }
344 344
345 345 /// @sa IDataSeries::minXAxisData()
346 346 DataSeriesIterator minXAxisData(double minXAxisData) const override
347 347 {
348 348 return std::lower_bound(
349 349 cbegin(), cend(), minXAxisData,
350 350 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
351 351 }
352 352
353 353 /// @sa IDataSeries::maxXAxisData()
354 354 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
355 355 {
356 356 // Gets the first element that greater than max value
357 357 auto it = std::upper_bound(
358 358 cbegin(), cend(), maxXAxisData,
359 359 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
360 360
361 361 return it == cbegin() ? cend() : --it;
362 362 }
363 363
364 364 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
365 365 double maxXAxisData) const override
366 366 {
367 367 if (minXAxisData > maxXAxisData) {
368 368 std::swap(minXAxisData, maxXAxisData);
369 369 }
370 370
371 371 auto begin = cbegin();
372 372 auto end = cend();
373 373
374 374 auto lowerIt = std::lower_bound(
375 375 begin, end, minXAxisData,
376 376 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
377 377 auto upperIt = std::upper_bound(
378 378 lowerIt, end, maxXAxisData,
379 379 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
380 380
381 381 return std::make_pair(lowerIt, upperIt);
382 382 }
383 383
384 384 std::pair<DataSeriesIterator, DataSeriesIterator>
385 385 valuesBounds(double minXAxisData, double maxXAxisData) const override
386 386 {
387 387 // Places iterators to the correct x-axis range
388 388 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
389 389
390 390 // Returns end iterators if the range is empty
391 391 if (xAxisRangeIts.first == xAxisRangeIts.second) {
392 392 return std::make_pair(cend(), cend());
393 393 }
394 394
395 395 // Gets the iterator on the min of all values data
396 396 auto minIt = std::min_element(
397 397 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
398 398 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
399 399 });
400 400
401 401 // Gets the iterator on the max of all values data
402 402 auto maxIt = std::max_element(
403 403 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
404 404 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
405 405 });
406 406
407 407 return std::make_pair(minIt, maxIt);
408 408 }
409 409
410 410 /// @return the y-axis associated to the data series
411 411 const OptionalAxis &yAxis() const { return m_YAxis; }
412 412 OptionalAxis &yAxis() { return m_YAxis; }
413 413
414 414 // /////// //
415 415 // Mutexes //
416 416 // /////// //
417 417
418 virtual void lockRead() { m_Lock.lockForRead(); }
419 virtual void lockWrite() { m_Lock.lockForWrite(); }
420 virtual void unlock() { m_Lock.unlock(); }
418 virtual QReadLocker getReadLock() override { return QReadLocker{&m_Lock}; }
419 virtual QWriteLocker getWriteLock() override { return QWriteLocker{&m_Lock}; }
420
421 virtual void lockRead() override { m_Lock.lockForRead(); }
422 virtual void lockWrite() override { m_Lock.lockForWrite(); }
423 virtual void unlock() override { m_Lock.unlock(); }
421 424
422 425 protected:
423 426 /// Protected ctor (DataSeries is abstract).
424 427 ///
425 428 /// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa
426 429 /// class description for consistent rules)
427 430 /// @remarks data series is automatically sorted on its x-axis data
428 431 /// @throws std::invalid_argument if the data are inconsistent with each other
429 432 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
430 433 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit,
431 434 OptionalAxis yAxis = OptionalAxis{})
432 435 : m_XAxisData{xAxisData},
433 436 m_XAxisUnit{xAxisUnit},
434 437 m_ValuesData{valuesData},
435 438 m_ValuesUnit{valuesUnit},
436 439 m_YAxis{std::move(yAxis)}
437 440 {
438 441 if (m_XAxisData->size() != m_ValuesData->size()) {
439 442 throw std::invalid_argument{
440 443 "The number of values by component must be equal to the number of x-axis data"};
441 444 }
442 445
443 446 // Validates y-axis (if defined)
444 447 if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) {
445 448 throw std::invalid_argument{
446 449 "As the y-axis is defined, the number of value components must be equal to the "
447 450 "number of y-axis data"};
448 451 }
449 452
450 453 // Sorts data if it's not the case
451 454 const auto &xAxisCData = m_XAxisData->cdata();
452 455 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
453 456 sort();
454 457 }
455 458 }
456 459
457 460 /// Copy ctor
458 461 explicit DataSeries(const DataSeries<Dim> &other)
459 462 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
460 463 m_XAxisUnit{other.m_XAxisUnit},
461 464 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
462 465 m_ValuesUnit{other.m_ValuesUnit},
463 466 m_YAxis{other.m_YAxis}
464 467 {
465 468 // Since a series is ordered from its construction and is always ordered, it is not
466 469 // necessary to call the sort method here ('other' is sorted)
467 470 }
468 471
469 472 /// Assignment operator
470 473 template <int D>
471 474 DataSeries &operator=(DataSeries<D> other)
472 475 {
473 476 std::swap(m_XAxisData, other.m_XAxisData);
474 477 std::swap(m_XAxisUnit, other.m_XAxisUnit);
475 478 std::swap(m_ValuesData, other.m_ValuesData);
476 479 std::swap(m_ValuesUnit, other.m_ValuesUnit);
477 480 std::swap(m_YAxis, other.m_YAxis);
478 481
479 482 return *this;
480 483 }
481 484
482 485 private:
483 486 /**
484 487 * Sorts data series on its x-axis data
485 488 */
486 489 void sort() noexcept
487 490 {
488 491 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
489 492 m_XAxisData = m_XAxisData->sort(permutation);
490 493 m_ValuesData = m_ValuesData->sort(permutation);
491 494 }
492 495
493 496 // x-axis
494 497 std::shared_ptr<ArrayData<1> > m_XAxisData;
495 498 Unit m_XAxisUnit;
496 499
497 500 // values
498 501 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
499 502 Unit m_ValuesUnit;
500 503
501 504 // y-axis (optional)
502 505 OptionalAxis m_YAxis;
503 506
504 507 QReadWriteLock m_Lock;
505 508 };
506 509
507 510 #endif // SCIQLOP_DATASERIES_H
@@ -1,102 +1,108
1 1 #ifndef SCIQLOP_IDATASERIES_H
2 2 #define SCIQLOP_IDATASERIES_H
3 3
4 #include <memory>
5
6 #include <QReadWriteLock>
7 #include <QString>
8
4 9 #include <Common/MetaTypes.h>
5 10 #include <Data/DataSeriesIterator.h>
6 11 #include <Data/DateTimeRange.h>
7 12 #include <Data/Unit.h>
13 #include <Common/deprecate.h>
8 14
9 #include <memory>
10
11 #include <QString>
12 15
13 16 template <int Dim>
14 17 class ArrayData;
15 18
16 19 /**
17 20 * @brief The IDataSeries aims to declare a data series.
18 21 *
19 22 * A data series is an entity that contains at least :
20 23 * - one dataset representing the x-axis
21 24 * - one dataset representing the values
22 25 *
23 26 * Each dataset is represented by an ArrayData, and is associated with a unit.
24 27 *
25 28 * An ArrayData can be unidimensional or two-dimensional, depending on the implementation of the
26 29 * IDataSeries. The x-axis dataset is always unidimensional.
27 30 *
28 31 * @sa ArrayData
29 32 */
30 33 class IDataSeries {
31 34 public:
32 35 virtual ~IDataSeries() noexcept = default;
33 36
34 37 /// Returns the x-axis dataset
35 38 virtual std::shared_ptr<ArrayData<1> > xAxisData() = 0;
36 39
37 40 /// Returns the x-axis dataset (as const)
38 41 virtual const std::shared_ptr<ArrayData<1> > xAxisData() const = 0;
39 42
40 43 virtual Unit xAxisUnit() const = 0;
41 44
42 45 /// @return the y-axis unit, if axis is defined, default unit otherwise
43 46 virtual Unit yAxisUnit() const = 0;
44 47
45 48 virtual Unit valuesUnit() const = 0;
46 49
47 50 virtual void merge(IDataSeries *dataSeries) = 0;
48 51 /// Removes from data series all entries whose value on the x-axis is not between min and max
49 52 virtual void purge(double min, double max) = 0;
50 53
51 54 /// @todo Review the name and signature of this method
52 55 virtual std::shared_ptr<IDataSeries> subDataSeries(const DateTimeRange &range) = 0;
53 56
54 57 virtual std::unique_ptr<IDataSeries> clone() const = 0;
55 58
56 59 /// @return the total number of points contained in the data series
57 60 virtual int nbPoints() const = 0;
58 61
59 62 /// @return the bounds of the y-axis axis (if defined)
60 63 virtual std::pair<double, double> yBounds() const = 0;
61 64
62 65 // ///////// //
63 66 // Iterators //
64 67 // ///////// //
65 68
66 69 virtual DataSeriesIterator cbegin() const = 0;
67 70 virtual DataSeriesIterator cend() const = 0;
68 71 virtual DataSeriesIterator begin() = 0;
69 72 virtual DataSeriesIterator end() = 0;
70 73
71 74 /// @return the iterator to the first entry of the data series whose x-axis data is greater than
72 75 /// or equal to the value passed in parameter, or the end iterator if there is no matching value
73 76 virtual DataSeriesIterator minXAxisData(double minXAxisData) const = 0;
74 77
75 78 /// @return the iterator to the last entry of the data series whose x-axis data is less than or
76 79 /// equal to the value passed in parameter, or the end iterator if there is no matching value
77 80 virtual DataSeriesIterator maxXAxisData(double maxXAxisData) const = 0;
78 81
79 82 /// @return the iterators pointing to the range of data whose x-axis values are between min and
80 83 /// max passed in parameters
81 84 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
82 85 xAxisRange(double minXAxisData, double maxXAxisData) const = 0;
83 86
84 87 /// @return two iterators pointing to the data that have respectively the min and the max value
85 88 /// data of a data series' range. The search is performed for a given x-axis range.
86 89 /// @sa xAxisRange()
87 90 virtual std::pair<DataSeriesIterator, DataSeriesIterator>
88 91 valuesBounds(double minXAxisData, double maxXAxisData) const = 0;
89 92
90 93 // /////// //
91 94 // Mutexes //
92 95 // /////// //
93
96 virtual QReadLocker getReadLock() = 0;
97 virtual QWriteLocker getWriteLock() = 0;
98 DEPRECATE(
94 99 virtual void lockRead() = 0;
95 100 virtual void lockWrite() = 0;
96 101 virtual void unlock() = 0;
102 )
97 103 };
98 104
99 105 // Required for using shared_ptr in signals/slots
100 106 SCIQLOP_REGISTER_META_TYPE(IDATASERIES_PTR_REGISTRY, std::shared_ptr<IDataSeries>)
101 107
102 108 #endif // SCIQLOP_IDATASERIES_H
@@ -1,34 +1,34
1 1 #ifndef SCIQLOP_VECTORSERIES_H
2 2 #define SCIQLOP_VECTORSERIES_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Data/DataSeries.h>
7 7
8 8 /**
9 9 * @brief The VectorSeries class is the implementation for a data series representing a vector.
10 10 */
11 11 class SCIQLOP_CORE_EXPORT VectorSeries : public DataSeries<2> {
12 12 public:
13 13 /**
14 14 * Ctor with three vectors (one per component). The vectors must have the same size, otherwise a
15 15 * ScalarSeries with no values will be created.
16 16 * @param xAxisData x-axis data
17 17 * @param xvaluesData x-values data
18 18 * @param yvaluesData y-values data
19 19 * @param zvaluesData z-values data
20 20 */
21 21 explicit VectorSeries(std::vector<double> xAxisData, std::vector<double> xValuesData,
22 22 std::vector<double> yValuesData, std::vector<double> zValuesData,
23 23 const Unit &xAxisUnit, const Unit &valuesUnit);
24 24
25 25 /// Default Ctor
26 26 explicit VectorSeries(std::vector<double> xAxisData, std::vector<double> valuesData,
27 27 const Unit &xAxisUnit, const Unit &valuesUnit);
28 28
29 std::unique_ptr<IDataSeries> clone() const;
29 std::unique_ptr<IDataSeries> clone() const override;
30 30
31 31 std::shared_ptr<IDataSeries> subDataSeries(const DateTimeRange &range) override;
32 32 };
33 33
34 34 #endif // SCIQLOP_VECTORSERIES_H
@@ -1,107 +1,112
1 1 #ifndef SCIQLOP_VARIABLE_H
2 2 #define SCIQLOP_VARIABLE_H
3 3
4 #include "CoreGlobal.h"
4 #include <QLoggingCategory>
5 #include <QObject>
6 #include <QUuid>
7 #include <QReadWriteLock>
5 8
9 #include "CoreGlobal.h"
6 10 #include <Data/DataSeriesIterator.h>
7 11 #include <Data/DataSeriesType.h>
8 12 #include <Data/DateTimeRange.h>
9 13
10 #include <QLoggingCategory>
11 #include <QObject>
12 #include <QUuid>
13 14
14 15 #include <Common/deprecate.h>
15 16 #include <Common/MetaTypes.h>
16 17 #include <Common/spimpl.h>
17 18
18 19 Q_DECLARE_LOGGING_CATEGORY(LOG_Variable)
19 20
20 21 class IDataSeries;
21 22 class QString;
22 23
23 24 /**
24 25 * @brief The Variable class represents a variable in SciQlop.
25 26 */
26 27 class SCIQLOP_CORE_EXPORT Variable : public QObject {
27 28
28 29 Q_OBJECT
29 30
30 31 public:
31 32 explicit Variable(const QString &name, const QVariantHash &metadata = {});
32 33
33 34 /// Copy ctor
34 35 explicit Variable(const Variable &other);
35 36
36 37 std::shared_ptr<Variable> clone() const;
37 38
38 39 QString name() const noexcept;
39 40 void setName(const QString &name) noexcept;
40 41 DateTimeRange range() const noexcept;
41 42 void setRange(const DateTimeRange &range, bool notify=false) noexcept;
42 43 DateTimeRange cacheRange() const noexcept;
43 44 void setCacheRange(const DateTimeRange &cacheRange) noexcept;
44 45
45 46 /// @return the number of points hold by the variable. The number of points is updated each time
46 47 /// the data series changes
47 48 unsigned int nbPoints() const noexcept;
48 49
49 50 /// Returns the real range of the variable, i.e. the min and max x-axis values of the data
50 51 /// series between the range of the variable. The real range is updated each time the variable
51 52 /// range or the data series changed
52 53 /// @return the real range, invalid range if the data series is null or empty
53 54 /// @sa setDataSeries()
54 55 /// @sa setRange()
55 56 std::optional<DateTimeRange> realRange() const noexcept;
56 57
57 58 /// @return the data of the variable, nullptr if there is no data
58 59 std::shared_ptr<IDataSeries> dataSeries() const noexcept;
59 60
60 61 /// @return the type of data that the variable holds
61 62 DataSeriesType type() const noexcept;
62 63
63 64 QVariantHash metadata() const noexcept;
64 65 DEPRECATE(
65 66 bool contains(const DateTimeRange &range) const noexcept;
66 67 bool intersect(const DateTimeRange &range) const noexcept;
67 68 bool isInside(const DateTimeRange &range) const noexcept;
68 69
69 70 bool cacheContains(const DateTimeRange &range) const noexcept;
70 71 bool cacheIntersect(const DateTimeRange &range) const noexcept;
71 72 bool cacheIsInside(const DateTimeRange &range) const noexcept;
72 73 )
73 74 DEPRECATE(
74 75 QVector<DateTimeRange> provideNotInCacheRangeList(const DateTimeRange &range) const noexcept;
75 76 QVector<DateTimeRange> provideInCacheRangeList(const DateTimeRange &range) const noexcept;
76 77 )
77 78 DEPRECATE(
78 79 void mergeDataSeries(std::shared_ptr<IDataSeries> dataSeries) noexcept;
79 80 )
80 void mergeDataSeries(IDataSeries* dataSeries, bool notify=false) noexcept;
81
82 void updateData(const std::vector<IDataSeries*>& dataSeries,
83 const DateTimeRange& newRange, const DateTimeRange& newCacheRange,
84 bool notify=true);
81 85
82 86 DEPRECATE(
83 87 static QVector<DateTimeRange> provideNotInCacheRangeList(const DateTimeRange &oldRange,
84 88 const DateTimeRange &nextRange);
85 89
86 90 static QVector<DateTimeRange> provideInCacheRangeList(const DateTimeRange &oldRange,
87 91 const DateTimeRange &nextRange);
88 92 )
89 93
90 94 QUuid ID(){return _uuid;}
91 95 signals:
92 96 void updated();
93 97 DEPRECATE(
94 98 /// Signal emitted when when the data series of the variable is loaded for the first time
95 99 void dataInitialized();
96 100 )
97 101 private:
98 102 class VariablePrivate;
99 103 spimpl::unique_impl_ptr<VariablePrivate> impl;
100 104 QUuid _uuid;
105 QReadWriteLock m_lock;
101 106 };
102 107
103 108 // Required for using shared_ptr in signals/slots
104 109 SCIQLOP_REGISTER_META_TYPE(VARIABLE_PTR_REGISTRY, std::shared_ptr<Variable>)
105 110 SCIQLOP_REGISTER_META_TYPE(VARIABLE_PTR_VECTOR_REGISTRY, QVector<std::shared_ptr<Variable> >)
106 111
107 112 #endif // SCIQLOP_VARIABLE_H
@@ -1,447 +1,436
1 1 #include <optional>
2 2 #include <QMutex>
3 3 #include <QReadWriteLock>
4 4 #include <QThread>
5 5
6 6 #include "Variable/Variable.h"
7 7
8 8 #include <Data/IDataSeries.h>
9 9 #include <Data/DateTimeRange.h>
10 10
11 11 #include <Common/debug.h>
12 12
13 13 Q_LOGGING_CATEGORY(LOG_Variable, "Variable")
14 14
15 namespace {
16 15
17 16 /**
18 17 * Searches in metadata for a value that can be converted to DataSeriesType
19 18 * @param metadata the metadata where to search
20 19 * @return the value converted to a DataSeriesType if it was found, UNKNOWN type otherwise
21 20 * @sa DataSeriesType
22 21 */
23 DataSeriesType findDataSeriesType(const QVariantHash &metadata)
22 static DataSeriesType findDataSeriesType(const QVariantHash &metadata)
24 23 {
25 24 auto dataSeriesType = DataSeriesType::UNKNOWN;
26 25
27 26 // Go through the metadata and stop at the first value that could be converted to DataSeriesType
28 27 for (auto it = metadata.cbegin(), end = metadata.cend();
29 28 it != end && dataSeriesType == DataSeriesType::UNKNOWN; ++it) {
30 29 dataSeriesType = DataSeriesTypeUtils::fromString(it.value().toString());
31 30 }
32 31
33 32 return dataSeriesType;
34 33 }
35 34
36 } // namespace
37 35
38 36 #define VP_PROPERTY(property,getter,setter,type) \
39 37 type getter() noexcept\
40 38 {\
41 39 QReadLocker lock{&m_Lock};\
42 40 return property;\
43 41 }\
44 42 void setter(const type& getter) noexcept\
45 43 {\
46 44 QWriteLocker lock{&m_Lock};\
47 45 property = getter;\
48 46 }\
49 47 type property;\
50 48
51 49 #define V_FW_GETTER_SETTER(getter,setter, type)\
52 50 type Variable::getter() const noexcept \
53 51 {\
54 52 return impl->getter();\
55 53 }\
56 54 void Variable::setter(const type& getter) noexcept \
57 55 {\
58 56 impl->setter(getter);\
59 57 }\
60 58
61 59 struct Variable::VariablePrivate {
62 60 explicit VariablePrivate(const QString &name, const QVariantHash &metadata)
63 61 : m_Name{name},
64 62 m_Range{INVALID_RANGE},
65 63 m_CacheRange{INVALID_RANGE},
66 64 m_Metadata{metadata},
67 65 m_DataSeries{nullptr},
68 66 m_RealRange{INVALID_RANGE},
69 67 m_NbPoints{0},
70 68 m_Type{findDataSeriesType(m_Metadata)}
71 69 {
72 70 }
73 71
74 72 VariablePrivate(const VariablePrivate &other)
75 73 : m_Name{other.m_Name},
76 74 m_Range{other.m_Range},
77 75 m_CacheRange{other.m_CacheRange},
78 76 m_Metadata{other.m_Metadata},
79 77 m_DataSeries{other.m_DataSeries != nullptr ? other.m_DataSeries->clone() : nullptr},
80 78 m_RealRange{other.m_RealRange},
81 79 m_NbPoints{other.m_NbPoints},
82 80 m_Type{findDataSeriesType(m_Metadata)}
83 81 {
84 82 }
85 83
86 84 void lockRead() { m_Lock.lockForRead(); }
87 85 void lockWrite() { m_Lock.lockForWrite(); }
88 86 void unlock() { m_Lock.unlock(); }
89 87
90 88 void purgeDataSeries()
91 89 {
92 90 if (m_DataSeries) {
93 91 m_DataSeries->purge(m_CacheRange.m_TStart, m_CacheRange.m_TEnd);
94 92 }
95 93 updateRealRange();
96 94 updateNbPoints();
97 95 }
98
96 void mergeDataSeries(const std::vector<IDataSeries*>& dataseries)
97 {
98 QWriteLocker lock{&m_Lock};
99 for(auto ds:dataseries)
100 {
101 if(m_DataSeries)
102 m_DataSeries->merge(ds);
103 else
104 m_DataSeries = ds->clone();
105 }
106 }
99 107 void updateNbPoints() { m_NbPoints = m_DataSeries ? m_DataSeries->nbPoints() : 0; }
100 108
101 109 /// Updates real range according to current variable range and data series
102 110 void updateRealRange()
103 111 {
104 112 if (m_DataSeries) {
105 m_DataSeries->lockRead();
113 auto lock = m_DataSeries->getReadLock();
106 114 auto end = m_DataSeries->cend();
107 115 auto minXAxisIt = m_DataSeries->minXAxisData(m_Range.m_TStart);
108 116 auto maxXAxisIt = m_DataSeries->maxXAxisData(m_Range.m_TEnd);
109
110 m_RealRange
111 = (minXAxisIt != end && maxXAxisIt != end && minXAxisIt->x() <= maxXAxisIt->x())
112 ? DateTimeRange{minXAxisIt->x(), maxXAxisIt->x()}
113 : INVALID_RANGE;
114 m_DataSeries->unlock();
117 if(minXAxisIt != end && maxXAxisIt != end && minXAxisIt->x() <= maxXAxisIt->x())
118 m_RealRange = DateTimeRange{minXAxisIt->x(), maxXAxisIt->x()};
119 else
120 m_RealRange = std::nullopt;
115 121 }
116 122 else {
117 123 m_RealRange = std::nullopt;
118 124 }
119 125 }
120 126
121 127 VP_PROPERTY(m_Name, name, setName, QString)
122 128 VP_PROPERTY(m_Range, range, setRange, DateTimeRange)
123 129 VP_PROPERTY(m_CacheRange, cacheRange, setCacheRange, DateTimeRange)
124 130 VP_PROPERTY(m_Metadata, metadata, setMetadata, QVariantHash)
125 131 VP_PROPERTY(m_DataSeries, dataSeries, setDataSeries, std::shared_ptr<IDataSeries>)
126 132 VP_PROPERTY(m_RealRange, realRange, setRealRange, std::optional<DateTimeRange>)
127 133 unsigned int m_NbPoints;
128 134 VP_PROPERTY(m_Type, type, setType, DataSeriesType)
129 135 QReadWriteLock m_Lock;
130 136 };
131 137
132 138 Variable::Variable(const QString &name, const QVariantHash &metadata)
133 139 : impl{spimpl::make_unique_impl<VariablePrivate>(name, metadata)},
134 140 _uuid{QUuid::createUuid()}
135 141 {
136 142 }
137 143
138 144 Variable::Variable(const Variable &other)
139 145 : impl{spimpl::make_unique_impl<VariablePrivate>(*other.impl)},
140 146 _uuid{QUuid::createUuid()} //is a clone but must have a != uuid
141 147 {
142 148 }
143 149
144 150 std::shared_ptr<Variable> Variable::clone() const
145 151 {
146 152 return std::make_shared<Variable>(*this);
147 153 }
148 154
149 155 V_FW_GETTER_SETTER(name,setName,QString)
150 156
151 157 DateTimeRange Variable::range() const noexcept
152 158 {
153 159 return impl->range();
154 160 }
155 161
156 162 void Variable::setRange(const DateTimeRange &range, bool notify) noexcept
157 163 {
158 164 impl->setRange(range);
159 165 impl->updateRealRange();
160 166 if(notify)
161 167 emit this->updated();
162 168 }
163 169
164 170 V_FW_GETTER_SETTER(cacheRange, setCacheRange, DateTimeRange)
165 171
166 172 unsigned int Variable::nbPoints() const noexcept
167 173 {
168 174 return impl->m_NbPoints;
169 175 }
170 176
171 177 std::optional<DateTimeRange> Variable::realRange() const noexcept
172 178 {
173 179 return impl->realRange();
174 180 }
175 181
176 182 void Variable::mergeDataSeries(std::shared_ptr<IDataSeries> dataSeries) noexcept
177 183 {
178 184 qCDebug(LOG_Variable()) << "TORM Variable::mergeDataSeries"
179 185 << QThread::currentThread()->objectName();
180 186 if (!dataSeries) {
181 187 /// @todo ALX : log
182 188 return;
183 189 }
184 190
185 191 auto dataInit = false;
186 192
187 193 // Add or merge the data
188 194 impl->lockWrite();
189 195 if (!impl->m_DataSeries) {
190 196 impl->m_DataSeries = dataSeries->clone();
191 197 dataInit = true;
192 198 }
193 199 else {
194 200 impl->m_DataSeries->merge(dataSeries.get());
195 201 }
196 202 impl->purgeDataSeries();
197 203 impl->unlock();
198 204
199 205 if (dataInit) {
200 206 emit dataInitialized();
201 207 }
202 208 }
203 209
204 void Variable::mergeDataSeries(IDataSeries *dataSeries, bool notify) noexcept
210 void Variable::updateData(const std::vector<IDataSeries *> &dataSeries, const DateTimeRange &newRange, const DateTimeRange &newCacheRange, bool notify)
205 211 {
206 if (dataSeries==nullptr) {
207 SCIQLOP_ERROR(Variable,"Given IDataSeries is nullptr");
208 return;
209 }
210
211 auto dataInit = false;
212 // @TODO move impl to Pimpl this is what it stands for...
213 // Add or merge the data
214 impl->lockWrite();
215 if (!impl->m_DataSeries) {
216 //@TODO find a better way
217 impl->m_DataSeries = dataSeries->clone();
218 dataInit = true;
219 delete dataSeries;
220 }
221 else {
222 impl->m_DataSeries->merge(dataSeries);
223 }
224 impl->purgeDataSeries();
225 impl->unlock();
226
227 if (dataInit) {
228 emit dataInitialized();
212 {
213 QWriteLocker lock{&m_lock};
214 impl->mergeDataSeries(dataSeries);
215 impl->setRange(newRange);
216 impl->setCacheRange(newCacheRange);
217 impl->purgeDataSeries();
229 218 }
230 219 if(notify)
231 emit this->updated();
220 emit updated();
232 221 }
233 222
234 223
235 224 std::shared_ptr<IDataSeries> Variable::dataSeries() const noexcept
236 225 {
237 226 return impl->dataSeries();
238 227 }
239 228
240 229 DataSeriesType Variable::type() const noexcept
241 230 {
242 231 return impl->type();
243 232 }
244 233
245 234 QVariantHash Variable::metadata() const noexcept
246 235 {
247 236 impl->lockRead();
248 237 auto metadata = impl->m_Metadata;
249 238 impl->unlock();
250 239 return metadata;
251 240 }
252 241
253 242 bool Variable::contains(const DateTimeRange &range) const noexcept
254 243 {
255 244 impl->lockRead();
256 245 auto res = impl->m_Range.contains(range);
257 246 impl->unlock();
258 247 return res;
259 248 }
260 249
261 250 bool Variable::intersect(const DateTimeRange &range) const noexcept
262 251 {
263 252
264 253 impl->lockRead();
265 254 auto res = impl->m_Range.intersect(range);
266 255 impl->unlock();
267 256 return res;
268 257 }
269 258
270 259 bool Variable::isInside(const DateTimeRange &range) const noexcept
271 260 {
272 261 impl->lockRead();
273 262 auto res = range.contains(DateTimeRange{impl->m_Range.m_TStart, impl->m_Range.m_TEnd});
274 263 impl->unlock();
275 264 return res;
276 265 }
277 266
278 267 bool Variable::cacheContains(const DateTimeRange &range) const noexcept
279 268 {
280 269 impl->lockRead();
281 270 auto res = impl->m_CacheRange.contains(range);
282 271 impl->unlock();
283 272 return res;
284 273 }
285 274
286 275 bool Variable::cacheIntersect(const DateTimeRange &range) const noexcept
287 276 {
288 277 impl->lockRead();
289 278 auto res = impl->m_CacheRange.intersect(range);
290 279 impl->unlock();
291 280 return res;
292 281 }
293 282
294 283 bool Variable::cacheIsInside(const DateTimeRange &range) const noexcept
295 284 {
296 285 impl->lockRead();
297 286 auto res = range.contains(DateTimeRange{impl->m_CacheRange.m_TStart, impl->m_CacheRange.m_TEnd});
298 287 impl->unlock();
299 288 return res;
300 289 }
301 290
302 291
303 292 QVector<DateTimeRange> Variable::provideNotInCacheRangeList(const DateTimeRange &range) const noexcept
304 293 {
305 294 // This code assume that cach in contigue. Can return 0, 1 or 2 SqpRange
306 295 auto notInCache = QVector<DateTimeRange>{};
307 296 if (impl->m_CacheRange != INVALID_RANGE) {
308 297
309 298 if (!this->cacheContains(range)) {
310 299 if (range.m_TEnd <= impl->m_CacheRange.m_TStart
311 300 || range.m_TStart >= impl->m_CacheRange.m_TEnd) {
312 301 notInCache << range;
313 302 }
314 303 else if (range.m_TStart < impl->m_CacheRange.m_TStart
315 304 && range.m_TEnd <= impl->m_CacheRange.m_TEnd) {
316 305 notInCache << DateTimeRange{range.m_TStart, impl->m_CacheRange.m_TStart};
317 306 }
318 307 else if (range.m_TStart < impl->m_CacheRange.m_TStart
319 308 && range.m_TEnd > impl->m_CacheRange.m_TEnd) {
320 309 notInCache << DateTimeRange{range.m_TStart, impl->m_CacheRange.m_TStart}
321 310 << DateTimeRange{impl->m_CacheRange.m_TEnd, range.m_TEnd};
322 311 }
323 312 else if (range.m_TStart < impl->m_CacheRange.m_TEnd) {
324 313 notInCache << DateTimeRange{impl->m_CacheRange.m_TEnd, range.m_TEnd};
325 314 }
326 315 else {
327 316 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
328 317 << QThread::currentThread();
329 318 }
330 319 }
331 320 }
332 321 else {
333 322 notInCache << range;
334 323 }
335 324
336 325 return notInCache;
337 326 }
338 327
339 328 QVector<DateTimeRange> Variable::provideInCacheRangeList(const DateTimeRange &range) const noexcept
340 329 {
341 330 // This code assume that cach in contigue. Can return 0 or 1 SqpRange
342 331
343 332 auto inCache = QVector<DateTimeRange>{};
344 333
345 334 if (impl->m_CacheRange != INVALID_RANGE) {
346 335
347 336 if (this->cacheIntersect(range)) {
348 337 if (range.m_TStart <= impl->m_CacheRange.m_TStart
349 338 && range.m_TEnd >= impl->m_CacheRange.m_TStart
350 339 && range.m_TEnd < impl->m_CacheRange.m_TEnd) {
351 340 inCache << DateTimeRange{impl->m_CacheRange.m_TStart, range.m_TEnd};
352 341 }
353 342
354 343 else if (range.m_TStart >= impl->m_CacheRange.m_TStart
355 344 && range.m_TEnd <= impl->m_CacheRange.m_TEnd) {
356 345 inCache << range;
357 346 }
358 347 else if (range.m_TStart > impl->m_CacheRange.m_TStart
359 348 && range.m_TEnd > impl->m_CacheRange.m_TEnd) {
360 349 inCache << DateTimeRange{range.m_TStart, impl->m_CacheRange.m_TEnd};
361 350 }
362 351 else if (range.m_TStart <= impl->m_CacheRange.m_TStart
363 352 && range.m_TEnd >= impl->m_CacheRange.m_TEnd) {
364 353 inCache << impl->m_CacheRange;
365 354 }
366 355 else {
367 356 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
368 357 << QThread::currentThread();
369 358 }
370 359 }
371 360 }
372 361
373 362 return inCache;
374 363 }
375 364
376 365
377 366 QVector<DateTimeRange> Variable::provideNotInCacheRangeList(const DateTimeRange &oldRange,
378 367 const DateTimeRange &nextRange)
379 368 {
380 369
381 370 // This code assume that cach in contigue. Can return 0, 1 or 2 SqpRange
382 371 auto notInCache = QVector<DateTimeRange>{};
383 372 if (oldRange != INVALID_RANGE) {
384 373
385 374 if (!oldRange.contains(nextRange)) {
386 375 if (nextRange.m_TEnd <= oldRange.m_TStart || nextRange.m_TStart >= oldRange.m_TEnd) {
387 376 notInCache << nextRange;
388 377 }
389 378 else if (nextRange.m_TStart < oldRange.m_TStart
390 379 && nextRange.m_TEnd <= oldRange.m_TEnd) {
391 380 notInCache << DateTimeRange{nextRange.m_TStart, oldRange.m_TStart};
392 381 }
393 382 else if (nextRange.m_TStart < oldRange.m_TStart && nextRange.m_TEnd > oldRange.m_TEnd) {
394 383 notInCache << DateTimeRange{nextRange.m_TStart, oldRange.m_TStart}
395 384 << DateTimeRange{oldRange.m_TEnd, nextRange.m_TEnd};
396 385 }
397 386 else if (nextRange.m_TStart < oldRange.m_TEnd) {
398 387 notInCache << DateTimeRange{oldRange.m_TEnd, nextRange.m_TEnd};
399 388 }
400 389 else {
401 390 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
402 391 << QThread::currentThread();
403 392 }
404 393 }
405 394 }
406 395 else {
407 396 notInCache << nextRange;
408 397 }
409 398
410 399 return notInCache;
411 400 }
412 401
413 402 QVector<DateTimeRange> Variable::provideInCacheRangeList(const DateTimeRange &oldRange,
414 403 const DateTimeRange &nextRange)
415 404 {
416 405 // This code assume that cach is contigue. Can return 0 or 1 SqpRange
417 406
418 407 auto inCache = QVector<DateTimeRange>{};
419 408
420 409 if (oldRange != INVALID_RANGE) {
421 410
422 411 if (oldRange.intersect(nextRange)) {
423 412 if (nextRange.m_TStart <= oldRange.m_TStart && nextRange.m_TEnd >= oldRange.m_TStart
424 413 && nextRange.m_TEnd < oldRange.m_TEnd) {
425 414 inCache << DateTimeRange{oldRange.m_TStart, nextRange.m_TEnd};
426 415 }
427 416
428 417 else if (nextRange.m_TStart >= oldRange.m_TStart
429 418 && nextRange.m_TEnd <= oldRange.m_TEnd) {
430 419 inCache << nextRange;
431 420 }
432 421 else if (nextRange.m_TStart > oldRange.m_TStart && nextRange.m_TEnd > oldRange.m_TEnd) {
433 422 inCache << DateTimeRange{nextRange.m_TStart, oldRange.m_TEnd};
434 423 }
435 424 else if (nextRange.m_TStart <= oldRange.m_TStart
436 425 && nextRange.m_TEnd >= oldRange.m_TEnd) {
437 426 inCache << oldRange;
438 427 }
439 428 else {
440 429 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
441 430 << QThread::currentThread();
442 431 }
443 432 }
444 433 }
445 434
446 435 return inCache;
447 436 }
@@ -1,200 +1,199
1 1 #include "Variable/VariableController2.h"
2 2 #include "Variable/VariableSynchronizationGroup2.h"
3 3 #include <Common/containers.h>
4 4 #include <Common/debug.h>
5 5 #include <Data/DataProviderParameters.h>
6 6 #include <Data/DateTimeRangeHelper.h>
7 7 #include <Variable/VariableCacheStrategyFactory.h>
8 8
9 9 class VariableController2::VariableController2Private
10 10 {
11 11 QMap<QUuid,std::shared_ptr<Variable>> _variables;
12 12 QMap<QUuid,std::shared_ptr<IDataProvider>> _providers;
13 13 QMap<QUuid,std::shared_ptr<VariableSynchronizationGroup2>> _synchronizationGroups;
14 14 std::unique_ptr<VariableCacheStrategy> _cacheStrategy;
15 15 bool p_contains(const std::shared_ptr<Variable>& variable)
16 16 {
17 17 return _providers.contains(variable->ID());
18 18 }
19 19 bool v_contains(const std::shared_ptr<Variable>& variable)
20 20 {
21 21 return SciQLop::containers::contains(this->_variables, variable);
22 22 }
23 23 bool sg_contains(const std::shared_ptr<Variable>& variable)
24 24 {
25 25 return _synchronizationGroups.contains(variable->ID());
26 26 }
27 27
28 28 void _changeRange(const std::shared_ptr<Variable>& var, DateTimeRange r)
29 29 {
30 30 auto provider = _providers[var->ID()];
31 31 DateTimeRange newCacheRange;
32 32 std::vector<DateTimeRange> missingRanges;
33 33 if(DateTimeRangeHelper::hasnan(var->cacheRange()))
34 34 {
35 35 newCacheRange = _cacheStrategy->computeRange(r,r);
36 36 missingRanges = {newCacheRange};
37 37 }
38 38 else
39 39 {
40 40 newCacheRange = _cacheStrategy->computeRange(var->cacheRange(),r);
41 41 missingRanges = newCacheRange - var->cacheRange();
42 42 }
43 std::vector<IDataSeries*> data;
43 44 for(auto range:missingRanges)
44 45 {
45 auto data = provider->getData(DataProviderParameters{{range},var->metadata()});
46 var->mergeDataSeries(data);
46 data.push_back(provider->getData(DataProviderParameters{{range},var->metadata()}));
47 47 }
48 var->setCacheRange(newCacheRange);
49 var->setRange(r);
48 var->updateData(data,r,newCacheRange,true);
50 49 }
51 50 public:
52 51 VariableController2Private(QObject* parent=Q_NULLPTR)
53 52 :_cacheStrategy(VariableCacheStrategyFactory::createCacheStrategy(CacheStrategy::SingleThreshold))
54 53 {
55 54 Q_UNUSED(parent);
56 55 }
57 56
58 57 ~VariableController2Private() = default;
59 58
60 59 std::shared_ptr<Variable> createVariable(const QString &name, const QVariantHash &metadata, std::shared_ptr<IDataProvider> provider)
61 60 {
62 61 auto newVar = std::make_shared<Variable>(name,metadata);
63 62 this->_variables[newVar->ID()] = newVar;
64 63 this->_providers[newVar->ID()] = std::move(provider);
65 64 this->_synchronizationGroups[newVar->ID()] = std::make_shared<VariableSynchronizationGroup2>(newVar->ID());
66 65 return newVar;
67 66 }
68 67
69 68 void deleteVariable(const std::shared_ptr<Variable>& variable)
70 69 {
71 70 /*
72 71 * Removing twice a var is ok but a var without provider has to be a hard error
73 72 * this means we got the var controller in an inconsistent state
74 73 */
75 74 if(v_contains(variable))
76 75 this->_variables.remove(variable->ID());
77 76 if(p_contains(variable))
78 77 this->_providers.remove(variable->ID());
79 78 else
80 79 SCIQLOP_ERROR(VariableController2Private, "No provider found for given variable");
81 80 }
82 81
83 82 void asyncChangeRange(const std::shared_ptr<Variable>& variable, const DateTimeRange& r)
84 83 {
85 84
86 85 }
87 86
88 87 void changeRange(const std::shared_ptr<Variable>& variable, DateTimeRange r)
89 88 {
90 89 if(p_contains(variable))
91 90 {
92 91 if(!DateTimeRangeHelper::hasnan(r))
93 92 {
94 93 auto group = _synchronizationGroups[variable->ID()];
95 94 if(auto transformation = DateTimeRangeHelper::computeTransformation(variable->range(),r);
96 95 transformation.has_value())
97 96 {
98 97 for(auto varId:group->variables())
99 98 {
100 99 auto var = _variables[varId];
101 100 auto newRange = var->range().transform(transformation.value());
102 101 _changeRange(var,newRange);
103 102 }
104 103 }
105 104 else // force new range to all variables -> may be weird if more than one var in the group
106 105 // @TODO ensure that there is no side effects
107 106 {
108 107 for(auto varId:group->variables())
109 108 {
110 109 auto var = _variables[varId];
111 110 _changeRange(var,r);
112 111 }
113 112 }
114 113 }
115 114 else
116 115 {
117 116 SCIQLOP_ERROR(VariableController2Private, "Invalid range containing NaN");
118 117 }
119 118 }
120 119 else
121 120 {
122 121 SCIQLOP_ERROR(VariableController2Private, "No provider found for given variable");
123 122 }
124 123 }
125 124
126 125 void synchronize(const std::shared_ptr<Variable>& var, const std::shared_ptr<Variable>& with)
127 126 {
128 127 if(v_contains(var) && v_contains(with))
129 128 {
130 129 if(sg_contains(var) && sg_contains(with))
131 130 {
132 131
133 132 auto dest_group = this->_synchronizationGroups[with->ID()];
134 133 this->_synchronizationGroups[var->ID()] = dest_group;
135 134 dest_group->addVariable(var->ID());
136 135 }
137 136 else
138 137 {
139 138 SCIQLOP_ERROR(VariableController2Private, "At least one of the given variables isn't in a sync group");
140 139 }
141 140 }
142 141 else
143 142 {
144 143 SCIQLOP_ERROR(VariableController2Private, "At least one of the given variables is not found");
145 144 }
146 145 }
147 146
148 147 const std::set<std::shared_ptr<Variable>> variables()
149 148 {
150 149 std::set<std::shared_ptr<Variable>> vars;
151 150 for(const auto &var:_variables)
152 151 {
153 152 vars.insert(var);
154 153 }
155 154 return vars;
156 155 }
157 156
158 157 };
159 158
160 159 VariableController2::VariableController2()
161 160 :impl{spimpl::make_unique_impl<VariableController2Private>()}
162 161 {}
163 162
164 163 std::shared_ptr<Variable> VariableController2::createVariable(const QString &name, const QVariantHash &metadata, const std::shared_ptr<IDataProvider>& provider, const DateTimeRange &range)
165 164 {
166 165 auto var = impl->createVariable(name, metadata, provider);
167 166 emit variableAdded(var);
168 167 if(!DateTimeRangeHelper::hasnan(range))
169 168 impl->changeRange(var,range);
170 169 else
171 170 SCIQLOP_ERROR(VariableController2, "Creating a variable with default constructed DateTimeRange is an error");
172 171 return var;
173 172 }
174 173
175 174 void VariableController2::deleteVariable(const std::shared_ptr<Variable>& variable)
176 175 {
177 176 impl->deleteVariable(variable);
178 177 emit variableDeleted(variable);
179 178 }
180 179
181 180 void VariableController2::changeRange(const std::shared_ptr<Variable>& variable, const DateTimeRange& r)
182 181 {
183 182 impl->changeRange(variable, r);
184 183 }
185 184
186 185 void VariableController2::asyncChangeRange(const std::shared_ptr<Variable> &variable, const DateTimeRange &r)
187 186 {
188 187 impl->asyncChangeRange(variable, r);
189 188 }
190 189
191 190 const std::set<std::shared_ptr<Variable> > VariableController2::variables()
192 191 {
193 192 return impl->variables();
194 193 }
195 194
196 195 void VariableController2::synchronize(const std::shared_ptr<Variable> &var, const std::shared_ptr<Variable> &with)
197 196 {
198 197 impl->synchronize(var, with);
199 198 }
200 199
@@ -1,76 +1,76
1 1 #include <QObject>
2 2 #include <QtTest>
3 3
4 4 #include <Data/IDataProvider.h>
5 5 #include <Time/TimeController.h>
6 6 #include <Variable/Variable.h>
7 7 #include <Variable/VariableController.h>
8 8
9 9 #include <memory>
10 10
11 11 namespace {
12 12
13 13 /// Provider used for the tests
14 14 class TestProvider : public IDataProvider {
15 std::shared_ptr<IDataProvider> clone() const { return std::make_shared<TestProvider>(); }
15 std::shared_ptr<IDataProvider> clone() const override { return std::make_shared<TestProvider>(); }
16 16
17 17 void requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters) override
18 18 {
19 19 // Does nothing
20 20 }
21 21
22 22 void requestDataAborting(QUuid acqIdentifier) override
23 23 {
24 24 // Does nothing
25 25 }
26 26 };
27 27
28 28 /// Generates a time controller for the tests
29 29 std::unique_ptr<TimeController> defaultTimeController()
30 30 {
31 31 auto timeController = std::make_unique<TimeController>();
32 32
33 33 QDateTime start{QDate{2017, 01, 01}, QTime{0, 0, 0, 0}};
34 34 QDateTime end{QDate{2017, 01, 02}, QTime{0, 0, 0, 0}};
35 35 timeController->setDateTimeRange(
36 36 DateTimeRange{DateUtils::secondsSinceEpoch(start), DateUtils::secondsSinceEpoch(end)});
37 37
38 38 return timeController;
39 39 }
40 40
41 41 } // namespace
42 42
43 43 class TestVariableController : public QObject {
44 44 Q_OBJECT
45 45
46 46 private slots:
47 47 void initTestCase() { QSKIP("Skip it"); }
48 48
49 49 /// Test removes variable from controller
50 50 void testDeleteVariable();
51 51 };
52 52
53 53 void TestVariableController::testDeleteVariable()
54 54 {
55 55 // Creates variable controller
56 56 auto timeController = defaultTimeController();
57 57 VariableController variableController{};
58 58 //variableController.setTimeController(timeController.get());
59 59
60 60 // Creates a variable from the controller
61 61 auto variable
62 62 = variableController.createVariable("variable", {}, std::make_shared<TestProvider>(), timeController->dateTime());
63 63
64 64 qDebug() << QString::number(variable.use_count());
65 65
66 66 // Removes the variable from the controller
67 67 variableController.deleteVariable(variable);
68 68
69 69 // Verifies that the variable has been deleted: this implies that the number of shared_ptr
70 70 // objects referring to the variable is 1 (the reference of this scope). Otherwise, the deletion
71 71 // is considered invalid since the variable is still referenced in the controller
72 72 QVERIFY(variable.use_count() == 1);
73 73 }
74 74
75 75 QTEST_MAIN(TestVariableController)
76 76 #include "TestVariableController.moc"
@@ -1,508 +1,508
1 1 #include <QObject>
2 2 #include <QtTest>
3 3
4 4 #include <memory>
5 5
6 6 #include <Data/DataProviderParameters.h>
7 7 #include <Data/IDataProvider.h>
8 8 #include <Data/ScalarSeries.h>
9 9 #include <Time/TimeController.h>
10 10 #include <Variable/Variable.h>
11 11 #include <Variable/VariableController.h>
12 12 #include <Variable/VariableModel.h>
13 13
14 14 namespace {
15 15
16 16 /// Delay after each operation on the variable before validating it (in ms)
17 17 const auto OPERATION_DELAY = 100;
18 18
19 19 /**
20 20 * Generates values according to a range. The value generated for a time t is the number of seconds
21 21 * of difference between t and a reference value (which is midnight -> 00:00:00)
22 22 *
23 23 * Example: For a range between 00:00:10 and 00:00:20, the generated values are
24 24 * {10,11,12,13,14,15,16,17,18,19,20}
25 25 */
26 26 std::vector<double> values(const DateTimeRange &range)
27 27 {
28 28 QTime referenceTime{0, 0};
29 29
30 30 std::vector<double> result{};
31 31
32 32 for (auto i = range.m_TStart; i <= range.m_TEnd; ++i) {
33 33 auto time = DateUtils::dateTime(i).time();
34 34 result.push_back(referenceTime.secsTo(time));
35 35 }
36 36
37 37 return result;
38 38 }
39 39
40 40 void validateRanges(VariableController &variableController,
41 41 const std::map<int, DateTimeRange> &expectedRanges)
42 42 {
43 43 for (const auto &expectedRangeEntry : expectedRanges) {
44 44 auto variableIndex = expectedRangeEntry.first;
45 45 auto expectedRange = expectedRangeEntry.second;
46 46
47 47 // Gets the variable in the controller
48 48 auto variable = variableController.variableModel()->variable(variableIndex);
49 49
50 50 // Compares variable's range to the expected range
51 51 QVERIFY(variable != nullptr);
52 52 auto range = variable->range();
53 53 qInfo() << "range vs expected range" << range << expectedRange;
54 54 QCOMPARE(range, expectedRange);
55 55
56 56 // Compares variable's data with values expected for its range
57 57 auto dataSeries = variable->dataSeries();
58 58 QVERIFY(dataSeries != nullptr);
59 59
60 60 auto it = dataSeries->xAxisRange(range.m_TStart, range.m_TEnd);
61 61 auto expectedValues = values(range);
62 62 qInfo() << std::distance(it.first, it.second) << expectedValues.size();
63 63 QVERIFY(std::equal(it.first, it.second, expectedValues.cbegin(), expectedValues.cend(),
64 64 [](const auto &dataSeriesIt, const auto &expectedValue) {
65 65 return dataSeriesIt.value() == expectedValue;
66 66 }));
67 67 }
68 68 }
69 69
70 70 /// Provider used for the tests
71 71 class TestProvider : public IDataProvider {
72 std::shared_ptr<IDataProvider> clone() const { return std::make_shared<TestProvider>(); }
72 std::shared_ptr<IDataProvider> clone() const override { return std::make_shared<TestProvider>(); }
73 73
74 74 void requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters) override
75 75 {
76 76 const auto &ranges = parameters.m_Times;
77 77
78 78 for (const auto &range : ranges) {
79 79 // Generates data series
80 80 auto valuesData = values(range);
81 81
82 82 std::vector<double> xAxisData{};
83 83 for (auto i = range.m_TStart; i <= range.m_TEnd; ++i) {
84 84 xAxisData.push_back(i);
85 85 }
86 86
87 87 auto dataSeries = std::make_shared<ScalarSeries>(
88 88 std::move(xAxisData), std::move(valuesData), Unit{"t", true}, Unit{});
89 89
90 90 emit dataProvided(acqIdentifier, dataSeries, range);
91 91 }
92 92 }
93 93
94 94 void requestDataAborting(QUuid acqIdentifier) override
95 95 {
96 96 // Does nothing
97 97 }
98 98 };
99 99
100 100 /**
101 101 * Interface representing an operation performed on a variable controller.
102 102 * This interface is used in tests to apply a set of operations and check the status of the
103 103 * controller after each operation
104 104 */
105 105 struct IOperation {
106 106 virtual ~IOperation() = default;
107 107 /// Executes the operation on the variable controller
108 108 virtual void exec(VariableController &variableController) const = 0;
109 109 };
110 110
111 111 /**
112 112 *Variable creation operation in the controller
113 113 */
114 114 struct Create : public IOperation {
115 115 explicit Create(int index, const DateTimeRange &range) : m_Index{index},m_range(range) {}
116 116
117 117 void exec(VariableController &variableController) const override
118 118 {
119 119 auto variable = variableController.createVariable(QString::number(m_Index), {},
120 120 std::make_unique<TestProvider>(), m_range);
121 121 }
122 122 int m_Index; ///< The index of the variable to create in the controller
123 123 DateTimeRange m_range;
124 124 };
125 125
126 126 /**
127 127 * Variable move/shift operation in the controller
128 128 */
129 129 struct Move : public IOperation {
130 130 explicit Move(int index, const DateTimeRange &newRange, bool shift = false, int delayMS = 10)
131 131 : m_Index{index}, m_NewRange{newRange}, m_Shift{shift}, m_DelayMs{delayMS}
132 132 {
133 133 }
134 134
135 135 void exec(VariableController &variableController) const override
136 136 {
137 137 if (auto variable = variableController.variableModel()->variable(m_Index)) {
138 138 variableController.onRequestDataLoading({variable}, m_NewRange, !m_Shift);
139 139 QTest::qWait(m_DelayMs);
140 140 }
141 141 }
142 142
143 143 int m_Index; ///< The index of the variable to move
144 144 DateTimeRange m_NewRange; ///< The new range of the variable
145 145 bool m_Shift; ///< Performs a shift (
146 146 int m_DelayMs; ///< wait the delay after running the request (
147 147 };
148 148
149 149 /**
150 150 * Variable synchronization/desynchronization operation in the controller
151 151 */
152 152 struct Synchronize : public IOperation {
153 153 explicit Synchronize(int index, QUuid syncId, bool synchronize = true)
154 154 : m_Index{index}, m_SyncId{syncId}, m_Synchronize{synchronize}
155 155 {
156 156 }
157 157
158 158 void exec(VariableController &variableController) const override
159 159 {
160 160 if (auto variable = variableController.variableModel()->variable(m_Index)) {
161 161 if (m_Synchronize) {
162 162 variableController.onAddSynchronized(variable, m_SyncId);
163 163 }
164 164 else {
165 165 variableController.desynchronize(variable, m_SyncId);
166 166 }
167 167 }
168 168 }
169 169
170 170 int m_Index; ///< The index of the variable to sync/desync
171 171 QUuid m_SyncId; ///< The synchronization group of the variable
172 172 bool m_Synchronize; ///< Performs sync or desync operation
173 173 };
174 174
175 175 /**
176 176 * Test Iteration
177 177 *
178 178 * A test iteration includes an operation to be performed, and a set of expected ranges after each
179 179 * operation. Each range is tested after the operation to ensure that:
180 180 * - the range of the variable is the expected range
181 181 * - the data of the variable are those generated for the expected range
182 182 */
183 183 struct Iteration {
184 184 std::shared_ptr<IOperation> m_Operation; ///< Operation to perform
185 185 std::map<int, DateTimeRange> m_ExpectedRanges; ///< Expected ranges (by variable index)
186 186 };
187 187
188 188 using Iterations = std::vector<Iteration>;
189 189
190 190 } // namespace
191 191
192 192 Q_DECLARE_METATYPE(Iterations)
193 193
194 194 class TestVariableSync : public QObject {
195 195 Q_OBJECT
196 196
197 197 private slots:
198 198 void initTestCase() { QSKIP("Temporarily disables TestVariableSync"); }
199 199
200 200 /// Input data for @sa testSync()
201 201 void testSync_data();
202 202
203 203 /// Input data for @sa testSyncOneVar()
204 204 void testSyncOneVar_data();
205 205
206 206 /// Tests synchronization between variables through several operations
207 207 void testSync();
208 208
209 209 /// Tests synchronization between variables through several operations
210 210 void testSyncOneVar();
211 211 };
212 212
213 213 namespace {
214 214
215 215 void testSyncCase1()
216 216 {
217 217 // Id used to synchronize variables in the controller
218 218 auto syncId = QUuid::createUuid();
219 219
220 220 /// Generates a range according to a start time and a end time (the date is the same)
221 221 auto range = [](const QTime &startTime, const QTime &endTime) {
222 222 return DateTimeRange{DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, startTime, Qt::UTC}),
223 223 DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, endTime, Qt::UTC})};
224 224 };
225 225
226 226 auto initialRange = range({12, 0}, {13, 0});
227 227
228 228 Iterations iterations{};
229 229 // Creates variables var0, var1 and var2
230 230 iterations.push_back({std::make_shared<Create>(0, initialRange), {{0, initialRange}}});
231 231 iterations.push_back({std::make_shared<Create>(1, initialRange), {{0, initialRange}, {1, initialRange}}});
232 232 iterations.push_back(
233 233 {std::make_shared<Create>(2, initialRange), {{0, initialRange}, {1, initialRange}, {2, initialRange}}});
234 234
235 235 // Adds variables into the sync group (ranges don't need to be tested here)
236 236 iterations.push_back({std::make_shared<Synchronize>(0, syncId)});
237 237 iterations.push_back({std::make_shared<Synchronize>(1, syncId)});
238 238 iterations.push_back({std::make_shared<Synchronize>(2, syncId)});
239 239
240 240 // Moves var0: ranges of var0, var1 and var2 change
241 241 auto newRange = range({12, 30}, {13, 30});
242 242 iterations.push_back(
243 243 {std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, newRange}}});
244 244
245 245 // Moves var1: ranges of var0, var1 and var2 change
246 246 newRange = range({13, 0}, {14, 0});
247 247 iterations.push_back(
248 248 {std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, newRange}}});
249 249
250 250 // Moves var2: ranges of var0, var1 and var2 change
251 251 newRange = range({13, 30}, {14, 30});
252 252 iterations.push_back(
253 253 {std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, newRange}}});
254 254
255 255 // Desyncs var2 and moves var0:
256 256 // - ranges of var0 and var1 change
257 257 // - range of var2 doesn't change anymore
258 258 auto var2Range = newRange;
259 259 newRange = range({13, 45}, {14, 45});
260 260 iterations.push_back({std::make_shared<Synchronize>(2, syncId, false)});
261 261 iterations.push_back(
262 262 {std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, var2Range}}});
263 263
264 264 // Shifts var0: although var1 is synchronized with var0, its range doesn't change
265 265 auto var1Range = newRange;
266 266 newRange = range({14, 45}, {15, 45});
267 267 iterations.push_back({std::make_shared<Move>(0, newRange, true),
268 268 {{0, newRange}, {1, var1Range}, {2, var2Range}}});
269 269
270 270 // Moves var0 through several operations:
271 271 // - range of var0 changes
272 272 // - range or var1 changes according to the previous shift (one hour)
273 273 auto moveVar0 = [&iterations](const auto &var0NewRange, const auto &var1ExpectedRange) {
274 274 iterations.push_back(
275 275 {std::make_shared<Move>(0, var0NewRange), {{0, var0NewRange}, {1, var1ExpectedRange}}});
276 276 };
277 277
278 278 // Pan left
279 279 moveVar0(range({14, 30}, {15, 30}), range({13, 30}, {14, 30}));
280 280 // Pan right
281 281 moveVar0(range({16, 0}, {17, 0}), range({15, 0}, {16, 0}));
282 282 // Zoom in
283 283 moveVar0(range({16, 30}, {16, 45}), range({15, 30}, {15, 45}));
284 284 // Zoom out
285 285 moveVar0(range({16, 15}, {17, 0}), range({15, 15}, {16, 0}));
286 286
287 287 QTest::newRow("sync1") << syncId << initialRange << std::move(iterations) << 200;
288 288 }
289 289
290 290 void testSyncCase2()
291 291 {
292 292 // Id used to synchronize variables in the controller
293 293 auto syncId = QUuid::createUuid();
294 294
295 295 /// Generates a range according to a start time and a end time (the date is the same)
296 296 auto dateTime = [](int year, int month, int day, int hours, int minutes, int seconds) {
297 297 return DateUtils::secondsSinceEpoch(
298 298 QDateTime{{year, month, day}, QTime{hours, minutes, seconds}, Qt::UTC});
299 299 };
300 300
301 301 auto initialRange = DateTimeRange{dateTime(2017, 1, 1, 12, 0, 0), dateTime(2017, 1, 1, 13, 0, 0)};
302 302
303 303 Iterations iterations{};
304 304 // Creates variables var0 and var1
305 305 iterations.push_back({std::make_shared<Create>(0, initialRange), {{0, initialRange}}});
306 306 iterations.push_back({std::make_shared<Create>(1, initialRange), {{0, initialRange}, {1, initialRange}}});
307 307
308 308 // Adds variables into the sync group (ranges don't need to be tested here)
309 309 iterations.push_back({std::make_shared<Synchronize>(0, syncId)});
310 310 iterations.push_back({std::make_shared<Synchronize>(1, syncId)});
311 311
312 312
313 313 // Moves var0 through several operations:
314 314 // - range of var0 changes
315 315 // - range or var1 changes according to the previous shift (one hour)
316 316 auto moveVar0 = [&iterations](const auto &var0NewRange) {
317 317 iterations.push_back(
318 318 {std::make_shared<Move>(0, var0NewRange), {{0, var0NewRange}, {1, var0NewRange}}});
319 319 };
320 320 moveVar0(DateTimeRange{dateTime(2017, 1, 1, 12, 0, 0), dateTime(2017, 1, 1, 13, 0, 0)});
321 321 moveVar0(DateTimeRange{dateTime(2017, 1, 1, 14, 0, 0), dateTime(2017, 1, 1, 15, 0, 0)});
322 322 moveVar0(DateTimeRange{dateTime(2017, 1, 1, 8, 0, 0), dateTime(2017, 1, 1, 9, 0, 0)});
323 323 // moveVar0(SqpRange{dateTime(2017, 1, 1, 7, 30, 0), dateTime(2017, 1, 1, 9, 30, 0)});
324 324 moveVar0(DateTimeRange{dateTime(2017, 1, 1, 2, 0, 0), dateTime(2017, 1, 1, 4, 0, 0)});
325 325 moveVar0(DateTimeRange{dateTime(2017, 1, 1, 6, 0, 0), dateTime(2017, 1, 1, 8, 0, 0)});
326 326
327 327 moveVar0(DateTimeRange{dateTime(2017, 1, 10, 6, 0, 0), dateTime(2017, 1, 15, 8, 0, 0)});
328 328 moveVar0(DateTimeRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 1, 25, 8, 0, 0)});
329 329 moveVar0(DateTimeRange{dateTime(2017, 1, 2, 6, 0, 0), dateTime(2017, 1, 8, 8, 0, 0)});
330 330
331 331 moveVar0(DateTimeRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)});
332 332 moveVar0(DateTimeRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)});
333 333 moveVar0(DateTimeRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)});
334 334 moveVar0(DateTimeRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)});
335 335 moveVar0(DateTimeRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)});
336 336 moveVar0(DateTimeRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)});
337 337 moveVar0(DateTimeRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)});
338 338 moveVar0(DateTimeRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)});
339 339 moveVar0(DateTimeRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)});
340 340 moveVar0(DateTimeRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)});
341 341 moveVar0(DateTimeRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)});
342 342 moveVar0(DateTimeRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)});
343 343
344 344
345 345 QTest::newRow("sync2") << syncId << initialRange << iterations << 4000;
346 346 // QTest::newRow("sync3") << syncId << initialRange << iterations << 5000;
347 347 }
348 348
349 349 void testSyncOnVarCase1()
350 350 {
351 351 // Id used to synchronize variables in the controller
352 352 auto syncId = QUuid::createUuid();
353 353
354 354 /// Generates a range according to a start time and a end time (the date is the same)
355 355 auto range = [](const QTime &startTime, const QTime &endTime) {
356 356 return DateTimeRange{DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, startTime, Qt::UTC}),
357 357 DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, endTime, Qt::UTC})};
358 358 };
359 359
360 360 auto initialRange = range({12, 0}, {13, 0});
361 361
362 362 Iterations creations{};
363 363 // Creates variables var0, var1 and var2
364 364 creations.push_back({std::make_shared<Create>(0, initialRange), {{0, initialRange}}});
365 365
366 366 Iterations synchronization{};
367 367 // Adds variables into the sync group (ranges don't need to be tested here)
368 368 synchronization.push_back({std::make_shared<Synchronize>(0, syncId)});
369 369
370 370 Iterations iterations{};
371 371
372 372 // Moves var0 through several operations
373 373 auto moveOp = [&iterations](const auto &requestedRange, const auto &expectedRange, auto delay) {
374 374 iterations.push_back(
375 375 {std::make_shared<Move>(0, requestedRange, true, delay), {{0, expectedRange}}});
376 376 };
377 377
378 378 // we assume here 300 ms is enough to finsh a operation
379 379 int delayToFinish = 300;
380 380 // jump to right, let's the operation time to finish
381 381 moveOp(range({14, 30}, {15, 30}), range({14, 30}, {15, 30}), delayToFinish);
382 382 // pan to right, let's the operation time to finish
383 383 moveOp(range({14, 45}, {15, 45}), range({14, 45}, {15, 45}), delayToFinish);
384 384 // jump to left, let's the operation time to finish
385 385 moveOp(range({03, 30}, {04, 30}), range({03, 30}, {04, 30}), delayToFinish);
386 386 // Pan to left, let's the operation time to finish
387 387 moveOp(range({03, 10}, {04, 10}), range({03, 10}, {04, 10}), delayToFinish);
388 388 // Zoom in, let's the operation time to finish
389 389 moveOp(range({03, 30}, {04, 00}), range({03, 30}, {04, 00}), delayToFinish);
390 390 // Zoom out left, let's the operation time to finish
391 391 moveOp(range({01, 10}, {18, 10}), range({01, 10}, {18, 10}), delayToFinish);
392 392 // Go back to initial range
393 393 moveOp(initialRange, initialRange, delayToFinish);
394 394
395 395
396 396 // jump to right, let's the operation time to finish
397 397 // moveOp(range({14, 30}, {15, 30}), initialRange, delayToFinish);
398 398 // Zoom out left, let's the operation time to finish
399 399 moveOp(range({01, 10}, {18, 10}), initialRange, delayToFinish);
400 400 // Go back to initial range
401 401 moveOp(initialRange, initialRange, 300);
402 402
403 403 QTest::newRow("syncOnVarCase1") << syncId << initialRange << std::move(creations)
404 404 << std::move(iterations);
405 405 }
406 406 }
407 407
408 408 void TestVariableSync::testSync_data()
409 409 {
410 410 // ////////////// //
411 411 // Test structure //
412 412 // ////////////// //
413 413
414 414 QTest::addColumn<QUuid>("syncId");
415 415 QTest::addColumn<DateTimeRange>("initialRange");
416 416 QTest::addColumn<Iterations>("iterations");
417 417 QTest::addColumn<int>("operationDelay");
418 418
419 419 // ////////// //
420 420 // Test cases //
421 421 // ////////// //
422 422
423 423 testSyncCase1();
424 424 testSyncCase2();
425 425 }
426 426
427 427 void TestVariableSync::testSyncOneVar_data()
428 428 {
429 429 // ////////////// //
430 430 // Test structure //
431 431 // ////////////// //
432 432
433 433 QTest::addColumn<QUuid>("syncId");
434 434 QTest::addColumn<DateTimeRange>("initialRange");
435 435 QTest::addColumn<Iterations>("creations");
436 436 QTest::addColumn<Iterations>("iterations");
437 437
438 438 // ////////// //
439 439 // Test cases //
440 440 // ////////// //
441 441
442 442 testSyncOnVarCase1();
443 443 }
444 444
445 445 void TestVariableSync::testSync()
446 446 {
447 447 // Inits controllers
448 448 TimeController timeController{};
449 449 VariableController variableController{};
450 450 //variableController.setTimeController(&timeController);
451 451
452 452 QFETCH(QUuid, syncId);
453 453 QFETCH(DateTimeRange, initialRange);
454 454 timeController.setDateTimeRange(initialRange);
455 455
456 456 // Synchronization group used
457 457 variableController.onAddSynchronizationGroupId(syncId);
458 458
459 459 // For each iteration:
460 460 // - execute operation
461 461 // - compare the variables' state to the expected states
462 462 QFETCH(Iterations, iterations);
463 463 QFETCH(int, operationDelay);
464 464 for (const auto &iteration : iterations) {
465 465 iteration.m_Operation->exec(variableController);
466 466 QTest::qWait(operationDelay);
467 467
468 468 validateRanges(variableController, iteration.m_ExpectedRanges);
469 469 }
470 470 }
471 471
472 472 void TestVariableSync::testSyncOneVar()
473 473 {
474 474 // Inits controllers
475 475 TimeController timeController{};
476 476 VariableController variableController{};
477 477 //variableController.setTimeController(&timeController);
478 478
479 479 QFETCH(QUuid, syncId);
480 480 QFETCH(DateTimeRange, initialRange);
481 481 timeController.setDateTimeRange(initialRange);
482 482
483 483 // Synchronization group used
484 484 variableController.onAddSynchronizationGroupId(syncId);
485 485
486 486 // For each iteration:
487 487 // - execute operation
488 488 // - compare the variables' state to the expected states
489 489 QFETCH(Iterations, iterations);
490 490 QFETCH(Iterations, creations);
491 491
492 492 for (const auto &creation : creations) {
493 493 creation.m_Operation->exec(variableController);
494 494 QTest::qWait(300);
495 495 }
496 496
497 497 for (const auto &iteration : iterations) {
498 498 iteration.m_Operation->exec(variableController);
499 499 }
500 500
501 501 if (!iterations.empty()) {
502 502 validateRanges(variableController, iterations.back().m_ExpectedRanges);
503 503 }
504 504 }
505 505
506 506 QTEST_MAIN(TestVariableSync)
507 507
508 508 #include "TestVariableSync.moc"
General Comments 0
You need to be logged in to leave comments. Login now