@@ -1,1 +1,1 | |||||
1 | Subproject commit 772f73227be2003fda7309a27d11cd0e248663ad |
|
1 | Subproject commit 3e1b5ebf191a92c416fb1f51ab071875008f0ff4 |
@@ -1,6 +1,9 | |||||
1 | #ifndef SCIQLOP_DATASERIESTYPE_H |
|
1 | #ifndef SCIQLOP_DATASERIESTYPE_H | |
2 | #define SCIQLOP_DATASERIESTYPE_H |
|
2 | #define SCIQLOP_DATASERIESTYPE_H | |
3 |
|
3 | |||
|
4 | #include <Data/ScalarTimeSerie.h> | |||
|
5 | #include <Data/SpectrogramTimeSerie.h> | |||
|
6 | #include <Data/VectorTimeSerie.h> | |||
4 | #include <QString> |
|
7 | #include <QString> | |
5 |
|
8 | |||
6 | enum class DataSeriesType |
|
9 | enum class DataSeriesType | |
@@ -30,6 +33,15 struct DataSeriesTypeUtils | |||||
30 | return DataSeriesType::NONE; |
|
33 | return DataSeriesType::NONE; | |
31 | } |
|
34 | } | |
32 | } |
|
35 | } | |
|
36 | static DataSeriesType type(TimeSeries::ITimeSerie* ts) | |||
|
37 | { | |||
|
38 | if(!ts) return DataSeriesType::NONE; | |||
|
39 | if(dynamic_cast<ScalarTimeSerie*>(ts)) return DataSeriesType::SCALAR; | |||
|
40 | if(dynamic_cast<VectorTimeSerie*>(ts)) return DataSeriesType::VECTOR; | |||
|
41 | if(dynamic_cast<SpectrogramTimeSerie*>(ts)) | |||
|
42 | return DataSeriesType::SPECTROGRAM; | |||
|
43 | return DataSeriesType::NONE; | |||
|
44 | } | |||
33 | }; |
|
45 | }; | |
34 |
|
46 | |||
35 | #endif // SCIQLOP_DATASERIESTYPE_H |
|
47 | #endif // SCIQLOP_DATASERIESTYPE_H |
@@ -49,6 +49,9 struct DateTimeRange | |||||
49 | { |
|
49 | { | |
50 | DateTimeRange() : m_TStart(std::nan("")), m_TEnd(std::nan("")) {} |
|
50 | DateTimeRange() : m_TStart(std::nan("")), m_TEnd(std::nan("")) {} | |
51 | DateTimeRange(double TStart, double TEnd) : m_TStart(TStart), m_TEnd(TEnd) {} |
|
51 | DateTimeRange(double TStart, double TEnd) : m_TStart(TStart), m_TEnd(TEnd) {} | |
|
52 | DateTimeRange(const std::pair<double, double>& range) | |||
|
53 | : m_TStart(range.first), m_TEnd(range.second) | |||
|
54 | {} | |||
52 | /// Creates SqpRange from dates and times |
|
55 | /// Creates SqpRange from dates and times | |
53 | static DateTimeRange fromDateTime(const QDate& startDate, |
|
56 | static DateTimeRange fromDateTime(const QDate& startDate, | |
54 | const QTime& startTime, |
|
57 | const QTime& startTime, |
@@ -19,11 +19,6 | |||||
19 | #include <TimeSeries.h> |
|
19 | #include <TimeSeries.h> | |
20 | #include <optional> |
|
20 | #include <optional> | |
21 |
|
21 | |||
22 | using AnyTimeSerie = |
|
|||
23 | variant_w_base<TimeSeries::ITimeSerie, |
|
|||
24 | std::variant<std::monostate, ScalarTimeSerie, |
|
|||
25 | VectorTimeSerie, SpectrogramTimeSerie>>; |
|
|||
26 |
|
||||
27 | class SCIQLOP_CORE_EXPORT Variable2 : public QObject |
|
22 | class SCIQLOP_CORE_EXPORT Variable2 : public QObject | |
28 | { |
|
23 | { | |
29 | Q_OBJECT |
|
24 | Q_OBJECT | |
@@ -45,7 +40,7 public: | |||||
45 | std::size_t nbPoints(); |
|
40 | std::size_t nbPoints(); | |
46 |
|
41 | |||
47 | /// @return the data of the variable, nullptr if there is no data |
|
42 | /// @return the data of the variable, nullptr if there is no data | |
48 | AnyTimeSerie* data(); |
|
43 | std::shared_ptr<TimeSeries::ITimeSerie> data(); | |
49 |
|
44 | |||
50 | /// @return the type of data that the variable holds |
|
45 | /// @return the type of data that the variable holds | |
51 | DataSeriesType type(); |
|
46 | DataSeriesType type(); |
@@ -71,7 +71,7 public: | |||||
71 | _variable->setData(data, _range, true); |
|
71 | _variable->setData(data, _range, true); | |
72 | else |
|
72 | else | |
73 | { |
|
73 | { | |
74 |
data.push_back(_variable->data() |
|
74 | data.push_back(_variable->data().get()); | |
75 | _variable->setData(data, _range, true); |
|
75 | _variable->setData(data, _range, true); | |
76 | } |
|
76 | } | |
77 | emit transactionComplete(); |
|
77 | emit transactionComplete(); |
@@ -43,6 +43,18 static DataSeriesType findDataSeriesType(const QVariantHash& metadata) | |||||
43 | return dataSeriesType; |
|
43 | return dataSeriesType; | |
44 | } |
|
44 | } | |
45 |
|
45 | |||
|
46 | std::shared_ptr<TimeSeries::ITimeSerie> | |||
|
47 | clone_ts(const std::shared_ptr<TimeSeries::ITimeSerie>& ts) | |||
|
48 | { | |||
|
49 | if(auto scal_ts = std::dynamic_pointer_cast<ScalarTimeSerie>(ts)) | |||
|
50 | return std::make_shared<ScalarTimeSerie>(*scal_ts); | |||
|
51 | if(auto scal_ts = std::dynamic_pointer_cast<VectorTimeSerie>(ts)) | |||
|
52 | return std::make_shared<VectorTimeSerie>(*scal_ts); | |||
|
53 | if(auto scal_ts = std::dynamic_pointer_cast<SpectrogramTimeSerie>(ts)) | |||
|
54 | return std::make_shared<SpectrogramTimeSerie>(*scal_ts); | |||
|
55 | return nullptr; | |||
|
56 | } | |||
|
57 | ||||
46 | struct Variable2::VariablePrivate |
|
58 | struct Variable2::VariablePrivate | |
47 | { |
|
59 | { | |
48 | VariablePrivate(const QString& name, const QVariantHash& metadata) |
|
60 | VariablePrivate(const QString& name, const QVariantHash& metadata) | |
@@ -52,51 +64,52 struct Variable2::VariablePrivate | |||||
52 | switch(findDataSeriesType(metadata)) |
|
64 | switch(findDataSeriesType(metadata)) | |
53 | { |
|
65 | { | |
54 | case DataSeriesType::SCALAR: |
|
66 | case DataSeriesType::SCALAR: | |
55 |
m_TimeSerie = std::make_ |
|
67 | m_TimeSerie = std::make_shared<ScalarTimeSerie>(ScalarTimeSerie{}); | |
56 | break; |
|
68 | break; | |
57 | case DataSeriesType::VECTOR: |
|
69 | case DataSeriesType::VECTOR: | |
58 |
m_TimeSerie = std::make_ |
|
70 | m_TimeSerie = std::make_shared<VectorTimeSerie>(VectorTimeSerie{}); | |
59 | break; |
|
71 | break; | |
60 | case DataSeriesType::SPECTROGRAM: |
|
72 | case DataSeriesType::SPECTROGRAM: | |
61 | m_TimeSerie = std::make_unique<AnyTimeSerie>(SpectrogramTimeSerie{}); |
|
73 | m_TimeSerie = | |
|
74 | std::make_shared<SpectrogramTimeSerie>(SpectrogramTimeSerie{}); | |||
62 | break; |
|
75 | break; | |
63 | default: break; |
|
76 | default: break; | |
64 | } |
|
77 | } | |
65 | } |
|
78 | } | |
66 |
|
79 | |||
67 |
VariablePrivate(const VariablePrivate& other) |
|
80 | VariablePrivate(const VariablePrivate& other) | |
|
81 | : m_Name{other.m_Name}, m_Range{other.m_Range}, | |||
|
82 | m_Metadata{other.m_Metadata}, m_RealRange{other.m_RealRange} | |||
|
83 | { | |||
|
84 | m_TimeSerie = clone_ts(other.m_TimeSerie); | |||
|
85 | } | |||
|
86 | ||||
68 | std::size_t nbPoints() |
|
87 | std::size_t nbPoints() | |
69 | { |
|
88 | { | |
70 |
if(m_TimeSerie) return m_TimeSerie-> |
|
89 | if(m_TimeSerie) return m_TimeSerie->size(); | |
71 | return 0; |
|
90 | return 0; | |
72 | } |
|
91 | } | |
73 | DataSeriesType type() const |
|
92 | DataSeriesType type() const | |
74 | { |
|
93 | { | |
75 |
|
|
94 | return DataSeriesTypeUtils::type(m_TimeSerie.get()); | |
76 | return DataSeriesType::NONE; |
|
|||
77 | } |
|
95 | } | |
78 |
|
96 | |||
79 | PROPERTY_(m_Name, name, setName, QString) |
|
97 | PROPERTY_(m_Name, name, setName, QString) | |
80 | PROPERTY_(m_Range, range, setRange, DateTimeRange) |
|
98 | PROPERTY_(m_Range, range, setRange, DateTimeRange) | |
81 | PROPERTY_(m_Metadata, metadata, setMetadata, QVariantHash) |
|
99 | PROPERTY_(m_Metadata, metadata, setMetadata, QVariantHash) | |
82 | PROPERTY_(m_RealRange, realRange, setRealRange, std::optional<DateTimeRange>) |
|
100 | PROPERTY_(m_RealRange, realRange, setRealRange, std::optional<DateTimeRange>) | |
83 |
|
|
101 | std::shared_ptr<TimeSeries::ITimeSerie> dataSeries() { return m_TimeSerie; } | |
84 |
void setDataSeries(std:: |
|
102 | void setDataSeries(std::shared_ptr<TimeSeries::ITimeSerie>&& timeSerie) | |
85 | { |
|
103 | { | |
86 | QWriteLocker lock{&m_Lock}; |
|
104 | QWriteLocker lock{&m_Lock}; | |
87 |
m_TimeSerie = |
|
105 | m_TimeSerie = timeSerie; | |
88 | if(m_TimeSerie->index() != 0) |
|
106 | if(m_TimeSerie) { setRealRange(DateTimeRange(m_TimeSerie->axis_range(0))); } | |
89 | { |
|
|||
90 | setRealRange(DateTimeRange( |
|
|||
91 | m_TimeSerie->base()->t(0), |
|
|||
92 | m_TimeSerie->base()->t(m_TimeSerie->base()->size() - 1))); |
|
|||
93 | } |
|
|||
94 | else |
|
107 | else | |
95 | { |
|
108 | { | |
96 | setRealRange(std::nullopt); |
|
109 | setRealRange(std::nullopt); | |
97 | } |
|
110 | } | |
98 | } |
|
111 | } | |
99 |
std:: |
|
112 | std::shared_ptr<TimeSeries::ITimeSerie> m_TimeSerie; | |
100 | QReadWriteLock m_Lock{QReadWriteLock::Recursive}; |
|
113 | QReadWriteLock m_Lock{QReadWriteLock::Recursive}; | |
101 | }; |
|
114 | }; | |
102 |
|
115 | |||
@@ -133,7 +146,10 std::optional<DateTimeRange> Variable2::realRange() | |||||
133 |
|
146 | |||
134 | std::size_t Variable2::nbPoints() { return impl->nbPoints(); } |
|
147 | std::size_t Variable2::nbPoints() { return impl->nbPoints(); } | |
135 |
|
148 | |||
136 | AnyTimeSerie* Variable2::data() { return impl->dataSeries(); } |
|
149 | std::shared_ptr<TimeSeries::ITimeSerie> Variable2::data() | |
|
150 | { | |||
|
151 | return impl->dataSeries(); | |||
|
152 | } | |||
137 |
|
153 | |||
138 | DataSeriesType Variable2::type() |
|
154 | DataSeriesType Variable2::type() | |
139 | { |
|
155 | { | |
@@ -143,81 +159,47 DataSeriesType Variable2::type() | |||||
143 |
|
159 | |||
144 | QVariantHash Variable2::metadata() const noexcept { return QVariantHash{}; } |
|
160 | QVariantHash Variable2::metadata() const noexcept { return QVariantHash{}; } | |
145 |
|
161 | |||
146 | // template<typename T> |
|
|||
147 | // std::unique_ptr<AnyTimeSerie> _merge(std::vector<AnyTimeSerie*> source) |
|
|||
148 | //{ |
|
|||
149 | // std::unique_ptr<AnyTimeSerie> dest = std::make_unique<AnyTimeSerie>(); |
|
|||
150 | // std::sort(std::begin(source), std::end(source), |
|
|||
151 | // [](AnyTimeSerie* a, AnyTimeSerie* b) { |
|
|||
152 | // return a->get<T>().front().t() < b->get<T>().front().t(); |
|
|||
153 | // }); |
|
|||
154 | // *dest = std::move(*source.front()); |
|
|||
155 | // std::for_each( |
|
|||
156 | // std::begin(source) + 1, std::end(source), [&dest](AnyTimeSerie* serie) { |
|
|||
157 | // std::copy(std::begin(serie->get<T>()), std::end(serie->get<T>()), |
|
|||
158 | // std::back_inserter(dest->get<T>())); |
|
|||
159 | // }); |
|
|||
160 | // return dest; |
|
|||
161 | //} |
|
|||
162 |
|
||||
163 | template<typename T> |
|
162 | template<typename T> | |
164 |
std:: |
|
163 | std::shared_ptr<TimeSeries::ITimeSerie> | |
165 | _merge(std::vector<TimeSeries::ITimeSerie*> source) |
|
164 | _merge(std::vector<TimeSeries::ITimeSerie*> source, const DateTimeRange& range) | |
166 | { |
|
165 | { | |
167 | std::unique_ptr<AnyTimeSerie> dest = std::make_unique<AnyTimeSerie>(); |
|
|||
168 | std::sort(std::begin(source), std::end(source), |
|
166 | std::sort(std::begin(source), std::end(source), | |
169 | [](TimeSeries::ITimeSerie* a, TimeSeries::ITimeSerie* b) { |
|
167 | [](TimeSeries::ITimeSerie* a, TimeSeries::ITimeSerie* b) { | |
170 | if(a->size() && b->size()) return a->t(0) < b->t(0); |
|
168 | if(a->size() && b->size()) return a->t(0) < b->t(0); | |
171 | return false; |
|
169 | return false; | |
172 | }); |
|
170 | }); | |
173 | *dest = std::move(*static_cast<T*>(source.front())); |
|
171 | std::shared_ptr<TimeSeries::ITimeSerie> dest = std::make_shared<T>(); | |
174 | std::for_each(std::begin(source) + 1, std::end(source), |
|
172 | std::for_each( | |
175 | [&dest](TimeSeries::ITimeSerie* serie) { |
|
173 | std::begin(source), std::end(source), | |
176 | // TODO -> remove overlap ! |
|
174 | [&dest, &range](TimeSeries::ITimeSerie* serie) { | |
177 |
|
|
175 | auto& ts = *static_cast<T*>(serie); | |
178 | std::end(*static_cast<T*>(serie)), |
|
176 | auto last_t = range.m_TStart; | |
179 | std::back_inserter(dest->get<T>())); |
|
177 | if(dest->size()) last_t = dest->axis(0).back(); | |
180 | }); |
|
178 | ||
|
179 | std::copy(std::upper_bound( | |||
|
180 | std::begin(ts), std::end(ts), last_t, | |||
|
181 | [](const auto& a, const auto& b) { return a < b.t(); }), | |||
|
182 | std::lower_bound( | |||
|
183 | std::begin(ts), std::end(ts), range.m_TEnd, | |||
|
184 | [](const auto& a, const auto& b) { return a.t() < b; }), | |||
|
185 | std::back_inserter(*std::dynamic_pointer_cast<T>(dest))); | |||
|
186 | }); | |||
181 | return dest; |
|
187 | return dest; | |
182 | } |
|
188 | } | |
183 |
|
189 | |||
184 | // std::unique_ptr<AnyTimeSerie> |
|
190 | std::shared_ptr<TimeSeries::ITimeSerie> | |
185 |
|
|
191 | merge(const std::vector<TimeSeries::ITimeSerie*>& dataSeries, | |
186 | //{ |
|
192 | const DateTimeRange& range) | |
187 | // switch(DataSeriesType(dataSeries.front()->index())) |
|
|||
188 | // { |
|
|||
189 | // case DataSeriesType::NONE: break; |
|
|||
190 | // case DataSeriesType::SCALAR: return _merge<ScalarTimeSerie>(dataSeries); |
|
|||
191 | // case DataSeriesType::VECTOR: return _merge<VectorTimeSerie>(dataSeries); |
|
|||
192 | // case DataSeriesType::SPECTROGRAM: |
|
|||
193 | // return _merge<SpectrogramTimeSerie>(dataSeries); |
|
|||
194 | // } |
|
|||
195 | // return std::unique_ptr<AnyTimeSerie>{}; |
|
|||
196 | //} |
|
|||
197 |
|
||||
198 | std::unique_ptr<AnyTimeSerie> |
|
|||
199 | merge(const std::vector<TimeSeries::ITimeSerie*>& dataSeries) |
|
|||
200 | { |
|
193 | { | |
201 | if(dynamic_cast<ScalarTimeSerie*>(dataSeries.front())) |
|
194 | if(dynamic_cast<ScalarTimeSerie*>(dataSeries.front())) | |
202 | return _merge<ScalarTimeSerie>(dataSeries); |
|
195 | return _merge<ScalarTimeSerie>(dataSeries, range); | |
203 | if(dynamic_cast<VectorTimeSerie*>(dataSeries.front())) |
|
196 | if(dynamic_cast<VectorTimeSerie*>(dataSeries.front())) | |
204 | return _merge<VectorTimeSerie>(dataSeries); |
|
197 | return _merge<VectorTimeSerie>(dataSeries, range); | |
205 | if(dynamic_cast<SpectrogramTimeSerie*>(dataSeries.front())) |
|
198 | if(dynamic_cast<SpectrogramTimeSerie*>(dataSeries.front())) | |
206 | return _merge<SpectrogramTimeSerie>(dataSeries); |
|
199 | return _merge<SpectrogramTimeSerie>(dataSeries, range); | |
207 |
return std:: |
|
200 | return std::shared_ptr<TimeSeries::ITimeSerie>{}; | |
208 | } |
|
201 | } | |
209 |
|
202 | |||
210 | // void Variable2::setData(const std::vector<AnyTimeSerie*>& dataSeries, |
|
|||
211 | // const DateTimeRange& range, bool notify) |
|
|||
212 | //{ |
|
|||
213 | // if(dataSeries.size()) |
|
|||
214 | // { |
|
|||
215 | // impl->setDataSeries(merge(dataSeries)); |
|
|||
216 | // impl->setRange(range); |
|
|||
217 | // if(notify) emit this->updated(this->ID()); |
|
|||
218 | // } |
|
|||
219 | //} |
|
|||
220 |
|
||||
221 | void Variable2::setData(const std::vector<TimeSeries::ITimeSerie*>& dataSeries, |
|
203 | void Variable2::setData(const std::vector<TimeSeries::ITimeSerie*>& dataSeries, | |
222 | const DateTimeRange& range, bool notify) |
|
204 | const DateTimeRange& range, bool notify) | |
223 | { |
|
205 | { | |
@@ -225,7 +207,7 void Variable2::setData(const std::vector<TimeSeries::ITimeSerie*>& dataSeries, | |||||
225 | { |
|
207 | { | |
226 | { |
|
208 | { | |
227 | QWriteLocker lock{&m_lock}; |
|
209 | QWriteLocker lock{&m_lock}; | |
228 | impl->setDataSeries(merge(dataSeries)); |
|
210 | impl->setDataSeries(merge(dataSeries, range)); | |
229 | impl->setRange(range); |
|
211 | impl->setRange(range); | |
230 | } |
|
212 | } | |
231 | if(notify) emit this->updated(this->ID()); |
|
213 | if(notify) emit this->updated(this->ID()); |
@@ -264,7 +264,7 class VariableController2::VariableController2Private | |||||
264 | data.push_back( |
|
264 | data.push_back( | |
265 | provider->getData(DataProviderParameters{{range}, var->metadata()})); |
|
265 | provider->getData(DataProviderParameters{{range}, var->metadata()})); | |
266 | } |
|
266 | } | |
267 |
data.push_back(var->data() |
|
267 | data.push_back(var->data().get()); // might be smarter | |
268 | var->setData(data, r, true); |
|
268 | var->setData(data, r, true); | |
269 | } |
|
269 | } | |
270 |
|
270 |
@@ -209,7 +209,8 PYBIND11_MODULE(pysciqlopcore, m) | |||||
209 | }) |
|
209 | }) | |
210 | .def("__repr__", __repr__<Variable>); |
|
210 | .def("__repr__", __repr__<Variable>); | |
211 |
|
211 | |||
212 |
py::class_<TimeSeries::ITimeSerie>( |
|
212 | py::class_<TimeSeries::ITimeSerie, std::shared_ptr<TimeSeries::ITimeSerie>>( | |
|
213 | m, "ITimeSerie") | |||
213 | .def_property_readonly( |
|
214 | .def_property_readonly( | |
214 | "size", [](const TimeSeries::ITimeSerie& ts) { return ts.size(); }) |
|
215 | "size", [](const TimeSeries::ITimeSerie& ts) { return ts.size(); }) | |
215 | .def("__len__", |
|
216 | .def("__len__", | |
@@ -309,12 +310,11 PYBIND11_MODULE(pysciqlopcore, m) | |||||
309 | .def_property("name", &Variable2::name, &Variable2::setName) |
|
310 | .def_property("name", &Variable2::name, &Variable2::setName) | |
310 | .def_property_readonly("range", &Variable2::range) |
|
311 | .def_property_readonly("range", &Variable2::range) | |
311 | .def_property_readonly("nbPoints", &Variable2::nbPoints) |
|
312 | .def_property_readonly("nbPoints", &Variable2::nbPoints) | |
312 |
.def_property_readonly( |
|
313 | .def_property_readonly( | |
313 | [](Variable2& var) -> TimeSeries::ITimeSerie* { |
|
314 | "data", | |
314 | auto data = var.data(); |
|
315 | [](Variable2& var) -> std::shared_ptr<TimeSeries::ITimeSerie> { | |
315 | if(data) return data->base(); |
|
316 | return var.data(); | |
316 | return nullptr; |
|
317 | }) | |
317 | }) |
|
|||
318 | .def("set_data", |
|
318 | .def("set_data", | |
319 | [](Variable2& var, std::vector<TimeSeries::ITimeSerie*> ts_list, |
|
319 | [](Variable2& var, std::vector<TimeSeries::ITimeSerie*> ts_list, | |
320 | const DateTimeRange& range) { var.setData(ts_list, range); }) |
|
320 | const DateTimeRange& range) { var.setData(ts_list, range); }) |
@@ -37,8 +37,7 public: | |||||
37 | auto serie = new ScalarTimeSerie(size); |
|
37 | auto serie = new ScalarTimeSerie(size); | |
38 | std::generate(std::begin(*serie), std::end(*serie), |
|
38 | std::generate(std::begin(*serie), std::end(*serie), | |
39 | [i = ceil(parameters.m_Range.m_TStart)]() mutable { |
|
39 | [i = ceil(parameters.m_Range.m_TStart)]() mutable { | |
40 | return std::pair<double, double>{i, i * slope}; |
|
40 | return std::pair<double, double>{i, i++ * slope}; | |
41 | i++; |
|
|||
42 | }); |
|
41 | }); | |
43 | return serie; |
|
42 | return serie; | |
44 | } |
|
43 | } | |
@@ -63,8 +62,9 template<int slope = 1> struct RangeType | |||||
63 | template<class T> |
|
62 | template<class T> | |
64 | void check_variable_state(std::shared_ptr<Variable2> v, DateTimeRange r) |
|
63 | void check_variable_state(std::shared_ptr<Variable2> v, DateTimeRange r) | |
65 | { |
|
64 | { | |
66 | QVERIFY(v->data()->base()->t(0) >= r.m_TStart); |
|
65 | auto range = v->data()->axis_range(0); | |
67 | QVERIFY(v->data()->base()->t(v->data()->base()->size() - 1) <= r.m_TEnd); |
|
66 | QVERIFY(range.first >= r.m_TStart); | |
|
67 | QVERIFY(range.second <= r.m_TEnd); | |||
68 | T::check_properties(v, r); |
|
68 | T::check_properties(v, r); | |
69 | } |
|
69 | } | |
70 |
|
70 |
General Comments 0
You need to be logged in to leave comments.
Login now