From 07b45ca4578ccaed91f7f3c70e82dc7ffbdf47ab Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Wed, 16 Jan 2019 06:11:48 +0100 Subject: spline: fix deadlock, logic error Tracking rarely deadlocked when saving mappings. Investigating it further also shown how a wrong bundle was used for Accela's splines. --- spline/axis-opts.cpp | 6 +- spline/axis-opts.hpp | 17 +-- spline/spline-widget.cpp | 83 +++++++------ spline/spline-widget.hpp | 8 +- spline/spline.cpp | 295 +++++++++++++++++++++++++---------------------- spline/spline.hpp | 57 +++++---- 6 files changed, 243 insertions(+), 223 deletions(-) (limited to 'spline') diff --git a/spline/axis-opts.cpp b/spline/axis-opts.cpp index fa527877..489008e6 100644 --- a/spline/axis-opts.cpp +++ b/spline/axis-opts.cpp @@ -27,14 +27,14 @@ static max_clamp get_max_y(Axis k) } axis_opts::axis_opts(QString pfx, Axis idx) : + prefix_(pfx), + axis_(idx), zero(b_settings_window, n(pfx, "zero-pos"), 0), src(b_settings_window, n(pfx, "source-index"), idx), invert(b_settings_window, n(pfx, "invert-sign"), false), altp(b_mapping_window, n(pfx, "alt-axis-sign"), false), clamp_x_(b_mapping_window, n(pfx, "max-value"), get_max_x(idx)), - clamp_y_(b_mapping_window, n(pfx, "max-output-value"), get_max_y(idx)), - prefix_(pfx), - axis_(idx) + clamp_y_(b_mapping_window, n(pfx, "max-output-value"), get_max_y(idx)) {} QString const& axis_opts::prefix() const { return prefix_; } diff --git a/spline/axis-opts.hpp b/spline/axis-opts.hpp index b4475502..c773dd61 100644 --- a/spline/axis-opts.hpp +++ b/spline/axis-opts.hpp @@ -9,8 +9,14 @@ namespace axis_opts_impl { using namespace options; -struct OTR_SPLINE_EXPORT axis_opts final +class OTR_SPLINE_EXPORT axis_opts final { + QString prefix_; + Axis axis_; + + static inline QString n(QString const& pfx, QString const& name); + +public: enum max_clamp { r180 = 180, @@ -37,8 +43,8 @@ struct OTR_SPLINE_EXPORT axis_opts final }; // note, these two bundles can be the same value with no issues - bundle b_settings_window = make_bundle("opentrack-ui"); - bundle b_mapping_window = make_bundle("opentrack-mappings"); + bundle b_settings_window{ make_bundle(axis_ == Axis(-1) ? QString() : "opentrack-ui") }; + bundle b_mapping_window{ make_bundle(axis_ == Axis(-1) ? QString() : "opentrack-mappings") }; value zero; value src; value invert, altp; @@ -49,11 +55,6 @@ struct OTR_SPLINE_EXPORT axis_opts final QString const& prefix() const; Axis axis() const; -private: - static inline QString n(QString const& pfx, QString const& name); - - QString prefix_; - Axis axis_; }; } // ns axis_opts_impl diff --git a/spline/spline-widget.cpp b/spline/spline-widget.cpp index bfe8aff1..d329d8e2 100644 --- a/spline/spline-widget.cpp +++ b/spline/spline-widget.cpp @@ -1,10 +1,3 @@ -/* Copyright (c) 2012-2016 Stanislaw Halik - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - #include "spline-widget.hpp" #include "compat/math.hpp" #include "compat/macros.hpp" @@ -19,7 +12,7 @@ #include -using namespace spline_detail; +namespace spline_detail { spline_widget::spline_widget(QWidget *parent) : QWidget(parent) { @@ -37,7 +30,7 @@ spline_widget::~spline_widget() } } -void spline_widget::setConfig(base_spline* spl) +void spline_widget::set_config(base_spline* spl) { if (connection) { @@ -49,13 +42,13 @@ void spline_widget::setConfig(base_spline* spl) if (spl) { - update_range(); - - std::shared_ptr s = spl->get_settings(); - connection = connect(s.get(), &base_spline::base_settings::recomputed, + std::shared_ptr s = spl->get_settings(); + connection = connect(s.get(), &base_settings::recomputed, this, [this] { reload_spline(); }, Qt::QueuedConnection); } + + reload_spline(); } QColor spline_widget::colorBezier() const @@ -71,7 +64,7 @@ void spline_widget::setColorBezier(QColor const& color) void spline_widget::force_redraw() { - background_img = QPixmap(); + background_img = {}; repaint(); } @@ -146,7 +139,6 @@ void spline_widget::drawBackground() #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) metrics.horizontalAdvance(text); #else - // XXX remove after Linux upgrades -sh 20181225 metrics.width(text); #endif @@ -162,7 +154,7 @@ void spline_widget::drawFunction() QPainter painter(&spline_img); painter.setRenderHint(QPainter::Antialiasing, true); - const points_t points = config->get_points(); + const points_t& points = config->get_points(); if (moving_control_point_idx >= 0 && moving_control_point_idx < points.size()) @@ -170,7 +162,7 @@ void spline_widget::drawFunction() const QPen pen(Qt::white, 1, Qt::SolidLine, Qt::FlatCap); const QPointF prev_ = point_to_pixel({}); QPointF prev(iround(prev_.x()), iround(prev_.y())); - for (auto point : points) + for (const auto& point : points) { const QPointF tmp = point_to_pixel(point); drawLine(painter, prev, tmp, pen); @@ -219,9 +211,9 @@ void spline_widget::drawFunction() for (double k = 0; k < maxx; k += step*3) // NOLINT { - const auto next_1 = (double) config->get_value_no_save(k + step*1); - const auto next_2 = (double) config->get_value_no_save(k + step*2); - const auto next_3 = (double) config->get_value_no_save(k + step*3); + const auto next_1 = config->get_value_no_save(k + step*1); + const auto next_2 = config->get_value_no_save(k + step*2); + const auto next_3 = config->get_value_no_save(k + step*3); QPointF b(clamp(point_to_pixel({k + step*1, next_1}))), c(clamp(point_to_pixel({k + step*2, next_2}))), @@ -235,14 +227,14 @@ void spline_widget::drawFunction() QPointF prev = point_to_pixel({}); for (double i = 0; i < maxx; i += step) // NOLINT { - const auto val = (double) config->get_value_no_save(i); + const auto val = config->get_value_no_save(i); const QPointF cur = point_to_pixel({i, val}); painter.drawLine(prev, cur); prev = cur; } { const double maxx = config->max_input(); - const double maxy = double(config->get_value_no_save(maxx)); + const double maxy = config->get_value_no_save(maxx); painter.drawLine(prev, point_to_pixel({ maxx, maxy })); } #endif @@ -281,10 +273,12 @@ void spline_widget::paintEvent(QPaintEvent *e) if (background_img.size() != QSize(W, H)) { - background_img = QPixmap(W, H); + update_range(); + + background_img = { W, H }; background_img.setDevicePixelRatio(dpr); - draw_function = true; drawBackground(); + draw_function = true; } if (draw_function) @@ -333,7 +327,7 @@ void spline_widget::mousePressEvent(QMouseEvent *e) moving_control_point_idx = -1; - points_t points = config->get_points(); + const points_t& points = config->get_points(); if (e->button() == Qt::LeftButton) { @@ -409,7 +403,7 @@ void spline_widget::mouseMoveEvent(QMouseEvent *e) } const int i = moving_control_point_idx; - const points_t points = config->get_points(); + const points_t& points = config->get_points(); const int sz = points.size(); if (i >= 0 && i < sz) @@ -501,7 +495,7 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e) void spline_widget::reload_spline() { // don't recompute here as the value's about to be recomputed in the callee - update_range(); + background_img = {}; update(); } @@ -519,7 +513,7 @@ void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_) double x = value.x(), y = value.y(); if (preview_only) - y = (double)config->get_value_no_save(x); + y = config->get_value_no_save(x); const int x_ = iround(x), y_ = iround(y); @@ -563,14 +557,15 @@ void spline_widget::update_range() const int mwl = 40, mhl = 20; const int mwr = 15, mhr = 35; - pixel_bounds = QRect(mwl, mhl, (w - mwl - mwr), (h - mhl - mhr)); - c = { pixel_bounds.width() / config->max_input(), pixel_bounds.height() / config->max_output() }; - draw_function = true; - - background_img = QPixmap(); - spline_img = QPixmap(); + pixel_bounds = { mwl, mhl, (w - mwl - mwr), (h - mhl - mhr) }; + c = { + pixel_bounds.width() / std::fmax(1, config->max_input()), + pixel_bounds.height() / std::fmax(1, config->max_output()) + }; - repaint(); + draw_function = true; + background_img = {}; + spline_img = {}; } bool spline_widget::point_within_pixel(const QPointF& pt, const QPointF& pixel) @@ -631,7 +626,7 @@ QPointF spline_widget::point_to_pixel(const QPointF& point) void spline_widget::resizeEvent(QResizeEvent *) { - update_range(); + reload_spline(); } bool spline_widget::is_on_pt(const QPointF& pos, int* pt) @@ -643,7 +638,7 @@ bool spline_widget::is_on_pt(const QPointF& pos, int* pt) return false; } - const points_t points = config->get_points(); + const points_t& points = config->get_points(); for (int i = 0; i < points.size(); i++) { @@ -659,3 +654,17 @@ bool spline_widget::is_on_pt(const QPointF& pos, int* pt) *pt = -1; return false; } + +void spline_widget::changeEvent(QEvent* e) +{ + switch (e->type()) + { + case QEvent::EnabledChange: + background_img = {}; spline_img = {}; + update(); + break; + default: + break; + } +} +} // ns spline_detail diff --git a/spline/spline-widget.hpp b/spline/spline-widget.hpp index a8adfce1..b0a76e2d 100644 --- a/spline/spline-widget.hpp +++ b/spline/spline-widget.hpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016 Stanislaw Halik +/* Copyright (c) 2012-2019 Stanislaw Halik * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -34,12 +34,11 @@ class OTR_SPLINE_EXPORT spline_widget final : public QWidget Q_PROPERTY(int x_step READ x_step WRITE set_x_step) Q_PROPERTY(int y_step READ y_step WRITE set_y_step) - using points_t = base_spline::points_t; public: explicit spline_widget(QWidget *parent = nullptr); ~spline_widget() override; - void setConfig(base_spline* spl); + void set_config(base_spline* spl); QColor colorBezier() const; void setColorBezier(QColor const& color); @@ -78,6 +77,7 @@ private: bool is_on_pt(const QPointF& pos, int* pt = nullptr); void update_range(); + void changeEvent(QEvent* e) override; QPointF pixel_to_point(const QPointF& point); QPointF point_to_pixel(const QPointF& point); @@ -103,7 +103,7 @@ private: bool draw_function = true, preview_only = false; // point's circle radius on the widget - static constexpr inline int point_size_in_pixels_ = 4; + static constexpr int point_size_in_pixels_ = 4; const double point_size_in_pixels = point_size_in_pixels_ * std::fmax(1, devicePixelRatioF() * .66); }; diff --git a/spline/spline.cpp b/spline/spline.cpp index 50812bad..3ec3b5dc 100644 --- a/spline/spline.cpp +++ b/spline/spline.cpp @@ -1,11 +1,3 @@ -/* Copyright (c) 2012-2016, Stanislaw Halik - - * Permission to use, copy, modify, and/or distribute this - * software for any purpose with or without fee is hereby granted, - * provided that the above copyright notice and this permission - * notice appear in all copies. - */ - #include "spline.hpp" #include "compat/math.hpp" @@ -18,7 +10,6 @@ #include #include -#include #include #include #include @@ -27,6 +18,7 @@ namespace spline_detail { +settings::~settings() = default; base_spline_::~base_spline_() = default; base_spline::~base_spline() = default; spline_modify_mixin::~spline_modify_mixin() = default; @@ -40,7 +32,6 @@ spline::spline(const QString& name, const QString& axis_name, Axis axis) spline::~spline() { QMutexLocker l(&mtx); - disconnect_signals(); } @@ -48,46 +39,53 @@ spline::spline() : spline(QString{}, QString{}, Axis(-1)) {} void spline::set_tracking_active(bool value) { - QMutexLocker l(&mtx); - activep = value; + std::shared_ptr S; + { + QMutexLocker l(&mtx); + S = s; + activep = value; + } + emit S->recomputed(); } bundle spline::get_bundle() { - QMutexLocker l(&mtx); // avoid logic errors due to changes in value data + QMutexLocker l(&mtx); return s->b; } void spline::clear() { + std::shared_ptr S; { QMutexLocker l(&mtx); + S = s; s->points = {}; + points = {}; + update_interp_data(); } - - invalidate_settings(); + emit S->recomputed(); } -float spline::get_value(double x) +double spline::get_value(double x) const { - QMutexLocker foo(&mtx); + QMutexLocker l(&mtx); - const float ret = get_value_no_save(x); - last_input_value.setX(std::fabs(x)); - last_input_value.setY(double(std::fabs(ret))); + const double ret = get_value_no_save(x); + last_input_value = { std::fabs(x), std::fabs((double)ret) }; return ret; } -float spline::get_value_no_save(double x) const +double spline::get_value_no_save(double x) const { - QMutexLocker foo(&mtx); + QMutexLocker l(&mtx); - float q = float(x * bucket_size_coefficient(points)); + double q = x * bucket_size_coefficient(points); int xi = (int)q; - float yi = get_value_internal(xi); - float yiplus1 = get_value_internal(xi+1); - float f = (q-xi); - float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation. + double yi = get_value_internal(xi); + double yiplus1 = get_value_internal(xi+1); + double f = (q-xi); + double ret = yiplus1 * f + yi * (1 - f); // at least do a linear interpolation. return ret; } @@ -95,43 +93,47 @@ bool spline::get_last_value(QPointF& point) { QMutexLocker foo(&mtx); point = last_input_value; - return activep; + return activep && point.y() >= 0; } -float spline::get_value_internal(int x) const +double spline::get_value_internal(int x) const { - if (!validp) - { - update_interp_data(); - validp = true; - } - const float sign = signum(x); x = std::abs(x); - const float ret_ = data[std::min(unsigned(x), unsigned(value_count)-1u)]; + const float ret_ = data[std::min(unsigned(x), value_count - 1)]; return sign * clamp(ret_, 0, 1000); } -QPointF spline::ensure_in_bounds(const QList& points, int i) +void spline::ensure_in_bounds(const QList& points, int i, f& x, f& y) { const int sz = points.size(); if (i < 0 || sz == 0) - return {}; - - if (i < sz) - return points[i]; - - return points[sz - 1]; + { + x = 0; + y = 0; + } + else if (i < sz) + { + const QPointF& pt = points[i]; + x = (f)pt.x(); + y = (f)pt.y(); + } + else + { + const QPointF& pt = points[sz - 1]; + x = (f)pt.x(); + y = (f)pt.y(); + } } int spline::element_count(const QList& points, double max_input) { const unsigned sz = (unsigned)points.size(); - for (unsigned k = 0; k < sz; k++) + for (int k = sz-1; k >= 0; k--) { const QPointF& pt = points[k]; - if (max_input > 1e-4 && pt.x() - 1e-2 > max_input) + if (max_input > 1e-4 && pt.x() - 1e-4 >= max_input) return k; } return sz; @@ -148,15 +150,11 @@ void spline::update_interp_data() const ensure_valid(list); const int sz = list.size(); - const double maxx = max_input(); - if (list.isEmpty()) - list.prepend({ maxx, max_output() }); + list.prepend({ max_input(), max_output() }); const double c = bucket_size_coefficient(list); - const double c_interp = c * 30; - - enum { magic_fill_value = -255 }; + const double c_ = c * c_interp; for (unsigned i = 0; i < value_count; i++) data[i] = magic_fill_value; @@ -166,35 +164,35 @@ void spline::update_interp_data() const const QPointF& pt = list[0]; const double x = pt.x(); const double y = pt.y(); - const unsigned max = clamp(uround(x * c), 0, value_count-2); + const unsigned max = clamp(uround(x * c), 0, value_count-1); for (unsigned k = 0; k <= max; k++) data[k] = float(y * k / max); // no need for bresenham } else { - if (list[0].x() > 1e-2 && list[0].x() <= maxx) - list.push_front(QPointF(0, 0)); + if (list[0].x() > 1e-2) + list.push_front({}); // now this is hella expensive due to `c_interp' for (int i = 0; i < sz; i++) { - const QPointF p0 = ensure_in_bounds(list, i - 1); - const QPointF p1 = ensure_in_bounds(list, i + 0); - const QPointF p2 = ensure_in_bounds(list, i + 1); - const QPointF p3 = ensure_in_bounds(list, i + 2); - const double p0_x = p0.x(), p1_x = p1.x(), p2_x = p2.x(), p3_x = p3.x(); - const double p0_y = p0.y(), p1_y = p1.y(), p2_y = p2.y(), p3_y = p3.y(); - - const double cx[4] = { + f p0_x, p1_x, p2_x, p3_x; + f p0_y, p1_y, p2_y, p3_y; + + ensure_in_bounds(list, i - 1, p0_x, p0_y); + ensure_in_bounds(list, i + 0, p1_x, p1_y); + ensure_in_bounds(list, i + 1, p2_x, p2_y); + ensure_in_bounds(list, i + 2, p3_x, p3_y); + + const f cx[4] = { 2 * p1_x, // 1 -p0_x + p2_x, // t 2 * p0_x - 5 * p1_x + 4 * p2_x - p3_x, // t^2 -p0_x + 3 * p1_x - 3 * p2_x + p3_x, // t3 }; - const double cy[4] = - { + const f cy[4] = { 2 * p1_y, // 1 -p0_y + p2_y, // t 2 * p0_y - 5 * p1_y + 4 * p2_y - p3_y, // t^2 @@ -202,36 +200,37 @@ void spline::update_interp_data() const }; // multiplier helps fill in all the x's needed - const unsigned end{int(c_interp * (p2_x - p1_x)) + 1u}; + const unsigned end = (unsigned)(c_ * (p2_x - p1_x)) + 1; + const f end_(end); for (unsigned k = 0; k <= end; k++) { - const double t = k / double(end); - const double t2 = t*t; - const double t3 = t*t*t; + const f t = k / end_; + const f t2 = t*t; + const f t3 = t*t*t; - const int x = int(.5 * c * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3)); - const float y = float(.5 * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3)); + const unsigned x = unsigned(f(.5) * c * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3)); + const float y = (float)(f(.5) * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3)); - if (unsigned(x) < value_count) + if (x < value_count) data[x] = y; } } } - double maxy = max_output(); + float maxy = (float)max_output(); float last = 0; #ifdef __clang__ # pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wfloat-equal" // stupid clang #endif - for (unsigned i = 0; i < unsigned(value_count); i++) + for (unsigned i = 0; i < value_count; i++) { if (data[i] == magic_fill_value) data[i] = last; - data[i] = clamp(data[i], 0, (float)maxy); + data[i] = clamp(data[i], 0, maxy); last = data[i]; } @@ -242,58 +241,72 @@ void spline::update_interp_data() const void spline::remove_point(int i) { - QMutexLocker foo(&mtx); + std::shared_ptr S; + { + QMutexLocker foo(&mtx); + S = s; - const int sz = element_count(points, max_input()); + const int sz = element_count(points, max_input()); - if (i >= 0 && i < sz) - { - points.erase(points.begin() + i); - s->points = points; - validp = false; + if (i >= 0 && i < sz) + { + points.erase(points.begin() + i); + s->points = points; + update_interp_data(); + } } + + emit S->recomputed(); } void spline::add_point(QPointF pt) { - QMutexLocker foo(&mtx); - - points.push_back(pt); - std::stable_sort(points.begin(), points.end(), sort_fn); - s->points = points; - validp = false; + std::shared_ptr S; + { + QMutexLocker foo(&mtx); + S = s; + points.push_back(pt); + std::stable_sort(points.begin(), points.end(), sort_fn); + s->points = points; + update_interp_data(); + } + emit S->recomputed(); } void spline::add_point(double x, double y) { - add_point(QPointF(x, y)); + add_point({ x, y }); } void spline::move_point(int idx, QPointF pt) { - QMutexLocker foo(&mtx); + std::shared_ptr S; + { + QMutexLocker foo(&mtx); + S = s; - const int sz = element_count(points, max_input()); + const int sz = element_count(points, max_input()); - if (idx >= 0 && idx < sz) - { - points[idx] = pt; - std::stable_sort(points.begin(), points.end(), sort_fn); - s->points = points; - validp = false; + if (idx >= 0 && idx < sz) + { + points[idx] = pt; + std::stable_sort(points.begin(), points.end(), sort_fn); + s->points = points; + update_interp_data(); + } } + emit S->recomputed(); } -const base_spline_::points_t& spline::get_points() const +const points_t& spline::get_points() const { - QMutexLocker foo(&mtx); return points; } int spline::get_point_count() const { QMutexLocker foo(&mtx); - return element_count(points, s->opts.clamp_x_); + return element_count(points, clamp_x); } void spline::reload() @@ -308,66 +321,69 @@ void spline::save() s->b->save(); } +void spline::invalidate_settings_() +{ + points = s->points; + clamp_x = s->opts.clamp_x_; + clamp_y = s->opts.clamp_y_; + update_interp_data(); +} + void spline::invalidate_settings() { + std::shared_ptr S; { QMutexLocker l(&mtx); - validp = false; - points = s->points; + S = s; + invalidate_settings_(); } - emit s->recomputed(); + emit S->recomputed(); } void spline::set_bundle(bundle b, const QString& axis_name, Axis axis) { - QMutexLocker l(&mtx); + if (!b) + b = make_bundle({}); + + std::shared_ptr S; - // gets called from ctor hence the need for nullptr checks - // the sentinel settings/bundle objects don't need any further branching once created - if (!s || s->b != b) { - disconnect_signals(); + QMutexLocker l(&mtx); - if (!b) - b = make_bundle(QString{}); + disconnect_signals(); s = std::make_shared(b, axis_name, axis); - - conn_changed = QObject::connect(b.get(), &bundle_::changed, - s.get(), [&] { invalidate_settings(); }, Qt::QueuedConnection); - // this isn't strictly necessary for the spline but helps the widget - conn_maxx = QObject::connect(&s->opts.clamp_x_, value_::value_changed(), - ctx.get(), [&](double) { invalidate_settings(); }, Qt::QueuedConnection); - conn_maxy = QObject::connect(&s->opts.clamp_y_, value_::value_changed(), - ctx.get(), [&](double) { invalidate_settings(); }, Qt::QueuedConnection); - - invalidate_settings(); + invalidate_settings_(); + S = s; + + conn_points = QObject::connect(&s->points, value_::value_changed>(), + ctx.get(), [this] { invalidate_settings(); }, Qt::DirectConnection); + conn_maxx = QObject::connect(&s->opts.clamp_x_, value_::value_changed(), + ctx.get(), [this](double) { invalidate_settings(); }, Qt::DirectConnection); + conn_maxy = QObject::connect(&s->opts.clamp_y_, value_::value_changed(), + ctx.get(), [this](double) { invalidate_settings(); }, Qt::DirectConnection); } + + emit S->recomputed(); } double spline::max_input() const { QMutexLocker l(&mtx); - using m = axis_opts::max_clamp; - const value& clamp = s->opts.clamp_x_; - if (clamp == m::x1000 && !points.empty()) - return points[points.size() - 1].x(); - return std::fabs(clamp.to()); + if (clamp_x == axis_opts::x1000) + return std::fmax(1, points.empty() ? 0 : points[points.size() - 1].x()); + return std::fabs((double)clamp_x); } double spline::max_output() const { QMutexLocker l(&mtx); - using m = axis_opts::max_clamp; - const value& clamp = s->opts.clamp_y_; - if (clamp == m::x1000 && !points.empty()) - return points[points.size() - 1].y(); - return std::fabs(clamp.to()); + if (clamp_y == axis_opts::x1000 && !points.empty()) + return std::fmax(1, points.empty() ? 0 : points[points.size() - 1].y()); + return std::fabs((double)clamp_y); } void spline::ensure_valid(points_t& list) const { - QMutexLocker foo(&mtx); - std::stable_sort(list.begin(), list.end(), sort_fn); const unsigned sz = (unsigned)list.size(); @@ -438,14 +454,15 @@ std::shared_ptr spline::get_settings() const double spline::bucket_size_coefficient(const QList& points) const { constexpr double eps = 1e-4; - const double maxx = max_input(); if (maxx < eps) return 0; - // needed to fill the buckets up to the last control point. - // space between that point and max_x doesn't matter. + // buckets are only used between 0 and the rightmost point's + // x coordinate. even if `clamp_x' is set to a much larger value, + // buckets retain more precision lower the x coordinate of the + // last point. const int sz = element_count(points, maxx); const double last_x = sz ? points[sz - 1].x() : maxx; @@ -455,19 +472,17 @@ double spline::bucket_size_coefficient(const QList& points) const void spline::disconnect_signals() { - if (conn_changed) + if (conn_points) { - QObject::disconnect(conn_changed); conn_changed = {}; + QObject::disconnect(conn_points); conn_points = {}; QObject::disconnect(conn_maxx); conn_maxx = {}; QObject::disconnect(conn_maxy); conn_maxy = {}; } } settings::settings(bundle const& b, const QString& axis_name, Axis idx): - b(b ? b : make_bundle("")), + b(b ? b : make_bundle({})), opts(axis_name, idx) {} -settings::~settings() = default; - } // ns spline_detail diff --git a/spline/spline.hpp b/spline/spline.hpp index e79dd8cc..a78db8dd 100644 --- a/spline/spline.hpp +++ b/spline/spline.hpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, Stanislaw Halik +/* Copyright (c) 2012-2019, Stanislaw Halik * Permission to use, copy, modify, and/or distribute this * software for any purpose with or without fee is hereby granted, @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -26,6 +25,7 @@ namespace spline_detail { +using points_t = QList; using namespace options; class OTR_SPLINE_EXPORT base_settings : public QObject @@ -49,10 +49,9 @@ public: struct OTR_SPLINE_EXPORT base_spline_ { virtual ~base_spline_(); - base_spline_& operator=(const base_spline_&) = default; - virtual float get_value(double x) = 0; - virtual float get_value_no_save(double x) const = 0; + virtual double get_value(double x) const = 0; + virtual double get_value_no_save(double x) const = 0; [[nodiscard]] virtual bool get_last_value(QPointF& point) = 0; virtual void set_tracking_active(bool value) = 0; @@ -60,18 +59,14 @@ struct OTR_SPLINE_EXPORT base_spline_ virtual double max_input() const = 0; virtual double max_output() const = 0; - using points_t = QList; - - virtual points_t const& get_points() const = 0; + virtual const points_t& get_points() const = 0; virtual int get_point_count() const = 0; }; struct OTR_SPLINE_EXPORT spline_settings_mixin { - using base_settings = spline_detail::base_settings; - - virtual std::shared_ptr get_settings() = 0; - virtual std::shared_ptr get_settings() const = 0; + virtual std::shared_ptr get_settings() = 0; + virtual std::shared_ptr get_settings() const = 0; virtual ~spline_settings_mixin(); }; @@ -94,31 +89,34 @@ struct OTR_SPLINE_EXPORT base_spline : base_spline_, spline_modify_mixin, spline class OTR_SPLINE_EXPORT spline : public base_spline { + using f = float; + double bucket_size_coefficient(const QList& points) const; void update_interp_data() const; - float get_value_internal(int x) const; + double get_value_internal(int x) const; static bool sort_fn(const QPointF& one, const QPointF& two); - static QPointF ensure_in_bounds(const QList& points, int i); + static void ensure_in_bounds(const QList& points, int i, f& x, f& y); static int element_count(const QList& points, double max_input); void disconnect_signals(); + void invalidate_settings_(); mutex mtx { mutex::Recursive }; - std::shared_ptr s; - QMetaObject::Connection conn_changed, conn_maxx, conn_maxy; - mutable std::vector data = std::vector(value_count, float(-16)); - mutable QPointF last_input_value; + std::shared_ptr s; + QMetaObject::Connection conn_points, conn_maxx, conn_maxy; std::shared_ptr ctx { std::make_shared() }; - // cached s->points + mutable QPointF last_input_value{-1, -1}; + mutable std::vector data = std::vector(value_count, magic_fill_value); mutable points_t points; - - static constexpr inline std::size_t value_count = 4096; - + mutable axis_opts::max_clamp clamp_x = axis_opts::x1000, clamp_y = axis_opts::x1000; mutable bool activep = false; - mutable bool validp = false; + + static constexpr unsigned value_count = 8192; + static constexpr float magic_fill_value = -(1 << 24) + 1; + static constexpr double c_interp = 5; public: void invalidate_settings(); @@ -134,11 +132,10 @@ public: spline(const QString& name, const QString& axis_name, Axis axis); ~spline() override; - spline& operator=(const spline&) = default; spline(const spline&) = default; - float get_value(double x) override; - float get_value_no_save(double x) const override; + double get_value(double x) const override; + double get_value_no_save(double x) const override; [[nodiscard]] bool get_last_value(QPointF& point) override; void add_point(QPointF pt) override; @@ -147,18 +144,16 @@ public: void remove_point(int i) override; void clear() override; - points_t const& get_points() const override; + const points_t& get_points() const override; void set_tracking_active(bool value) override; bundle get_bundle(); void ensure_valid(points_t& in_out) const; - std::shared_ptr get_settings() override; - std::shared_ptr get_settings() const override; + std::shared_ptr get_settings() override; + std::shared_ptr get_settings() const override; int get_point_count() const override; - - using settings = spline_detail::settings; }; } // ns spline_detail -- cgit v1.2.3