diff --git a/benchmark/src/benchmark_frame.cpp b/benchmark/src/benchmark_frame.cpp index e6351c6..8644ad5 100644 --- a/benchmark/src/benchmark_frame.cpp +++ b/benchmark/src/benchmark_frame.cpp @@ -257,6 +257,8 @@ void render_benchmark_frame( adjusted_reserved_height, params.adjusted_preview_height, false, // show_info + ctx.render_config.skip_gl_calls, // skip_gl + ctx.render_config.dark_mode, // dark_mode &ctx.render_config }; }(); diff --git a/include/vnm_plot/core/algo.h b/include/vnm_plot/core/algo.h index 1c699a2..dc14709 100644 --- a/include/vnm_plot/core/algo.h +++ b/include/vnm_plot/core/algo.h @@ -374,13 +374,11 @@ std::size_t upper_bound_timestamp( inline std::size_t choose_lod_level( const std::vector& scales, - std::size_t current_level, double base_pps) { if (scales.empty() || !(base_pps > 0.0)) { return 0; } - (void)current_level; constexpr double target_pps = 1.0; std::size_t best_level = 0; diff --git a/include/vnm_plot/core/asset_loader.h b/include/vnm_plot/core/asset_loader.h index d997794..6efc696 100644 --- a/include/vnm_plot/core/asset_loader.h +++ b/include/vnm_plot/core/asset_loader.h @@ -36,9 +36,6 @@ class Asset_loader // If set, assets will first be searched in this directory. void set_override_directory(std::string_view path); - // Get the override directory (empty if not set) - [[nodiscard]] std::string_view override_directory() const noexcept; - // Register an embedded asset // The data must remain valid for the lifetime of the Asset_loader. void register_embedded(std::string_view name, std::string_view data); diff --git a/include/vnm_plot/core/constants.h b/include/vnm_plot/core/constants.h index 5283299..22d8fc5 100644 --- a/include/vnm_plot/core/constants.h +++ b/include/vnm_plot/core/constants.h @@ -15,7 +15,6 @@ namespace detail { constexpr float k_v_label_horizontal_padding_px = 6.0f; // Grid & preview metrics -constexpr float k_grid_line_half_px = 0.7f; constexpr float k_cell_span_min_factor = 0.1f; constexpr float k_cell_span_max_factor = 6.0f; @@ -28,7 +27,6 @@ constexpr float k_hit_test_px = 1.0f; // Grid appearance constexpr float k_grid_line_alpha_base = 0.75f; -constexpr int k_max_grid_levels = 32; // Value formatting constexpr int k_value_decimals = 3; @@ -50,8 +48,6 @@ constexpr double k_default_font_px = 10.0; constexpr double k_default_base_label_height_px = 14.0; // Internal constants -constexpr int k_drawn_x_reserve = 512; -constexpr int k_index_growth_step = 2; constexpr double k_vbar_width_change_threshold_d = 0.5; constexpr double k_vbar_min_width_px_d = 1.0; constexpr int k_rect_initial_quads = 256; diff --git a/include/vnm_plot/core/function_sample.h b/include/vnm_plot/core/function_sample.h index 66fb2dd..9b2e6a1 100644 --- a/include/vnm_plot/core/function_sample.h +++ b/include/vnm_plot/core/function_sample.h @@ -34,8 +34,6 @@ struct function_sample_t y_min(y_min_), y_max(y_max_) {} - - double timestamp() const { return x; } }; #pragma pack(pop) @@ -76,40 +74,6 @@ class Function_data_source : public Vector_data_source set_data(std::move(samples)); } - // Generate with range (for band/envelope display) - void generate_with_range( - Function fn_value, - Function fn_min, - Function fn_max, - double x_min, - double x_max, - size_t num_samples) - { - if (num_samples < 2) { - num_samples = 2; - } - - std::vector samples; - samples.reserve(num_samples); - - const double step = (x_max - x_min) / static_cast(num_samples - 1); - - for (size_t i = 0; i < num_samples; ++i) { - double x = x_min + i * step; - float y = fn_value(x); - float y_lo = fn_min(x); - float y_hi = fn_max(x); - - // Handle NaN/Inf - if (!std::isfinite(y)) { y = 0.0f; } - if (!std::isfinite(y_lo)) { y_lo = y; } - if (!std::isfinite(y_hi)) { y_hi = y; } - - samples.emplace_back(x, y, y_lo, y_hi); - } - - set_data(std::move(samples)); - } }; // ----------------------------------------------------------------------------- @@ -142,9 +106,4 @@ inline Data_access_policy_typed make_function_sample_policy_t return policy; } -inline Data_access_policy make_function_sample_policy() -{ - return make_function_sample_policy_typed().erase(); -} - } // namespace vnm::plot diff --git a/include/vnm_plot/core/layout_calculator.h b/include/vnm_plot/core/layout_calculator.h index 6978c9a..e7aaca5 100644 --- a/include/vnm_plot/core/layout_calculator.h +++ b/include/vnm_plot/core/layout_calculator.h @@ -12,10 +12,8 @@ #include namespace vnm::plot { -class Profiler; -} -namespace vnm::plot { +class Profiler; // ----------------------------------------------------------------------------- // Layout Calculator @@ -78,7 +76,6 @@ class Layout_calculator int vertical_seed_index = -1; double vertical_seed_step = 0.0; double vertical_finest_step = 0.0; - double horizontal_finest_step = 0.0; int horizontal_seed_index = -1; double horizontal_seed_step = 0.0; }; diff --git a/include/vnm_plot/core/range_cache.h b/include/vnm_plot/core/range_cache.h index b89137c..de71a6a 100644 --- a/include/vnm_plot/core/range_cache.h +++ b/include/vnm_plot/core/range_cache.h @@ -81,29 +81,21 @@ bool validate_range_cache_impl( inline bool validate_range_cache_sequences( const std::map>& series_map, std::unordered_map& cache_map, - Auto_v_range_mode auto_mode) + Auto_v_range_mode auto_mode, + bool preview = false) { return validate_range_cache_impl(series_map, cache_map, auto_mode, - [](const series_data_t& s) + [preview](const series_data_t& s) -> std::pair { + if (preview) { + if (s.preview_matches_main()) return {nullptr, nullptr}; + Data_source* src = s.preview_source(); + if (!src) return {nullptr, nullptr}; + return {src, &s.preview_access()}; + } if (!s.data_source) return {nullptr, nullptr}; return {s.data_source.get(), &s.access}; }); } -inline bool validate_preview_range_cache_sequences( - const std::map>& series_map, - std::unordered_map& cache_map, - Auto_v_range_mode auto_mode) -{ - return validate_range_cache_impl(series_map, cache_map, auto_mode, - [](const series_data_t& s) - -> std::pair { - if (s.preview_matches_main()) return {nullptr, nullptr}; - Data_source* src = s.preview_source(); - if (!src) return {nullptr, nullptr}; - return {src, &s.preview_access()}; - }); -} - } // namespace vnm::plot diff --git a/include/vnm_plot/core/series_renderer.h b/include/vnm_plot/core/series_renderer.h index 90fbe45..1fff22e 100644 --- a/include/vnm_plot/core/series_renderer.h +++ b/include/vnm_plot/core/series_renderer.h @@ -6,7 +6,6 @@ #include "gl_program.h" #include "types.h" -#include #include #include #include @@ -45,14 +44,6 @@ class Series_renderer const std::map>& series); private: - struct metrics_t - { - std::atomic bytes_uploaded{0}; - std::atomic bytes_allocated{0}; - std::atomic vbo_reallocations{0}; - std::atomic snapshot_failures{0}; - }; - struct vbo_view_state_t { GLuint id = UINT_MAX; @@ -157,7 +148,6 @@ class Series_renderer std::unique_ptr m_pipe_area; std::unique_ptr m_pipe_colormap; - metrics_t m_metrics; uint64_t m_frame_id = 0; // Monotonic frame counter for snapshot caching std::shared_ptr get_or_load_shader( diff --git a/include/vnm_plot/core/types.h b/include/vnm_plot/core/types.h index dbd1286..6acde9c 100644 --- a/include/vnm_plot/core/types.h +++ b/include/vnm_plot/core/types.h @@ -37,11 +37,6 @@ struct Size_2i { return width == other.width && height == other.height; } - - [[nodiscard]] constexpr bool operator!=(const Size_2i& other) const noexcept - { - return !(*this == other); - } }; // ----------------------------------------------------------------------------- @@ -466,18 +461,6 @@ struct series_data_t return access; } - void set_preview_source(std::shared_ptr source) - { - ensure_preview_config(); - preview_config->data_source.set(std::move(source)); - } - - void set_preview_source_ref(Data_source& source) - { - ensure_preview_config(); - preview_config->data_source.set_ref(source); - } - // Preview style (falls back to main when unset). Display_style effective_preview_style() const { @@ -502,13 +485,6 @@ struct series_data_t && effective_preview_style() == style; } -private: - void ensure_preview_config() - { - if (!preview_config) { - preview_config = preview_config_t{}; - } - } }; // ----------------------------------------------------------------------------- @@ -591,10 +567,6 @@ struct layout_cache_key_t font_metrics_key == other.font_metrics_key; } - [[nodiscard]] bool operator!=(const layout_cache_key_t& other) const noexcept - { - return !(*this == other); - } }; class Layout_cache @@ -653,6 +625,8 @@ struct frame_context_t double adjusted_preview_height = 0.0; bool show_info = false; + bool skip_gl = false; + bool dark_mode = false; const Plot_config* config = nullptr; }; diff --git a/include/vnm_plot/qt/vnm_qt_safe_dispatch.h b/include/vnm_plot/qt/vnm_qt_safe_dispatch.h index ade6961..0e775b5 100644 --- a/include/vnm_plot/qt/vnm_qt_safe_dispatch.h +++ b/include/vnm_plot/qt/vnm_qt_safe_dispatch.h @@ -3,79 +3,26 @@ #include #include #include -#include #include -#include #include #include -#include -#include #include #include #include /** * @file vnm_qt_safe_dispatch.h - * @brief Provides a set of type-safe, const-correct, and thread-safe helpers - * for invoking methods and emitting signals on QObjects, ensuring calls - * are dispatched to the object's thread. + * @brief Type-safe helper for posting method calls to a QObject's thread. * - * @section usage_examples Usage Examples - * - * @subsection ex_invoke Invoking Methods - * Replace verbose, string-based calls with type-safe, single-line alternatives. * @code * // Fire-and-forget a method call: * vnm::post_invoke(m_plot, &VNM_plot::adjust_t_to_target, new_min, new_max); - * - * // Blocking call with return value and error handling: - * try { - * int new_id = vnm::blocking_invoke(m_plot, &VNM_plot::create_data_source, data); - * ls_debug() << "Data source attached with ID: " << new_id; - * } - * catch (const std::exception& e) { - * qWarning() << "Failed to attach data source: " << e.what(); - * } - * @endcode - * - * @subsection ex_emit Emitting Signals - * Safely emit signals from any thread, ensuring they originate from the emitter's thread. - * @code - * // Fire-and-forget a signal emission: - * vnm::post_emit(this, &MyClass::data_ready, 42, "hello"); - * - * // Atomically emit a batch of signals from the emitter's thread: - * vnm::post_emit_batch(this, [](MyClass* emitter) { - * emitter->first_signal(1); - * emitter->second_signal("foo"); - * emitter->third_signal(true); - * }); * @endcode * - * @section main_features Main Features & Requirements - * - Requires the target object's thread to have a running event loop for queued/blocking calls. - * - No `Q_ARG` or `QMetaType` registration is needed; arguments are marshalled via lambda capture. - * - Arguments are copied or moved into a tuple for cross-thread calls. - * - * @section exceptions Exception Handling Contract - * - `safe_invoke`, `safe_emit`: Rethrow on same thread. Queued path swallows target-thread exceptions - * (logs a critical error and asserts in debug builds). May throw on caller's thread during argument capture. - * - `post_invoke`, `post_emit`, `post_emit_batch`: Never throw to the caller. Exceptions are handled internally. - * - `blocking_invoke`: Propagates exceptions from the receiver thread to the caller. - * - `try_blocking_invoke`: Catches all exceptions and returns an error state (`false` or empty `std::optional`). - * - * @section warnings Important Warnings - * @warning **Argument Lifetimes**: Avoid passing non-owning views (e.g., `QStringView`, `std::string_view`) - * to queued calls (`safe_*`, `post_*`) unless the underlying data is guaranteed to outlive the call's + * @warning **Argument Lifetimes**: Avoid passing non-owning views (e.g., `std::string_view`) + * to queued calls unless the underlying data is guaranteed to outlive the call's * execution on the target thread. Prefer passing owning copies. - * @warning **Reentrancy and Deadlocks**: - * - `safe_invoke`/`safe_emit` may execute synchronously. Be mindful of reentrancy. - * - `blocking_invoke` will deadlock if the receiver's thread is busy or lacks a running event loop. - * @warning **Reference Returns**: `blocking_invoke` and `try_blocking_invoke` do not support methods - * that return references. Return by value or smart pointer instead. - * @warning **C++ Access Control**: Qt `signals:` are `protected`. To use `safe_emit` or `post_emit` - * from outside the class hierarchy, provide a public `Q_INVOKABLE` wrapper method in the emitter class. */ namespace vnm { @@ -89,7 +36,7 @@ template constexpr T* to_raw(QPointer const* p) noexcept { return p template struct member_class; #ifndef VNM_NODISCARD -# ifdef VNM_STRICT_API // define in CI or debug-only if you like +# ifdef VNM_STRICT_API # define VNM_NODISCARD [[nodiscard]] # else # define VNM_NODISCARD @@ -114,11 +61,8 @@ VNM_FOR_ALL_REFQUAL(noexcept) // with noexcept #undef VNM_FOR_ALL_REFQUAL #undef VNM_MEMBER_QUAL -// member_class_t yields the class `C` with the method's cv/ref qualifiers. template using member_class_t = typename member_class::type; -// this_ptr_t yields the corresponding `C*` (with cv qualifiers) -// expected by the member function pointer. template using this_ptr_t = std::add_pointer_t< std::remove_reference_t< member_class_t @@ -134,89 +78,6 @@ constexpr std::decay_t decay_copy(T&& v) noexcept(std::is_nothrow_constructib } // namespace detail -//================================================================================================// -// METHOD INVOCATION -//================================================================================================// - -/** - * @brief Invokes a method directly if on the same thread, otherwise queues it. - * @return True if the call was invoked directly or successfully queued. - * @return False if the receiver object was null at the time of the call. - */ -template -VNM_NODISCARD bool safe_invoke(ObjLike&& obj_like, Method method, Args&&... args) -{ - auto* raw_cv = detail::to_raw(std::forward(obj_like)); - if (!raw_cv) { - return false; - } - - using raw_type_cv = std::remove_pointer_t; - using base_type = std::remove_cv_t; - using this_ptr = detail::this_ptr_t; - - static_assert(std::is_member_function_pointer_v, - "Method must be a pointer-to-member function"); - static_assert(std::is_base_of_v, - "Receiver must inherit QObject"); - static_assert(std::is_base_of_v>, base_type>, - "Method not in object's class hierarchy"); - static_assert(std::is_invocable_v, - "Method cannot be invoked with provided arguments"); - static_assert(((std::is_copy_constructible_v> - || std::is_rvalue_reference_v) && ...), - "Each lvalue argument must be copy-constructible; pass rvalues (std::move) for move-only types."); - - constexpr bool receiver_is_const = std::is_const_v; - constexpr bool method_is_const = std::is_const_v< - std::remove_reference_t< - detail::member_class_t - > - >; - static_assert(!receiver_is_const || method_is_const, - "Cannot invoke a non-const method on a const receiver"); - - if (QThread::currentThread() == raw_cv->thread()) { - std::invoke(method, static_cast(raw_cv), std::forward(args)...); - return true; - } - - QPointer guard{const_cast(raw_cv)}; - return QMetaObject::invokeMethod( - const_cast(raw_cv), - [guard, method, tup = std::make_tuple( - vnm::detail::decay_copy(std::forward(args))...)]() mutable - { - auto* obj = guard.data(); - if (!obj) { - return; - } - try { - std::apply( - [&](auto&&... u){ - std::invoke( - method, - static_cast(obj), - std::forward(u)... - ); - }, - std::move(tup) - ); - } - catch (...) { -#ifdef QT_DEBUG - qCritical("safe_invoke: Unhandled exception in target thread is a critical bug."); - Q_ASSERT_X(false, "vnm::safe_invoke", "Exception escaped target thread"); -#else - qWarning("safe_invoke: Exception swallowed in queued call."); -#endif - } - }, - Qt::QueuedConnection - ); -} - - /** * @brief Posts a method for asynchronous invocation (always queued). "Fire-and-forget". * @return True if the call was successfully queued. @@ -290,420 +151,7 @@ VNM_NODISCARD bool post_invoke(ObjLike&& obj_like, Method method, Args&&... args } } - -/** - * @brief Invokes a method and blocks until it completes, returning the result. - * @return The result of the invoked method. - * @throws std::runtime_error on failure to invoke, or rethrows any exception from the target method. - */ -template -auto blocking_invoke(ObjLike&& obj_like, Method method, Args&&... args) - -> std::invoke_result_t, Args...> -{ - auto* raw_cv = detail::to_raw(std::forward(obj_like)); - if (!raw_cv) { - throw std::runtime_error("blocking_invoke: Receiver object is null."); - } - - using raw_type_cv = std::remove_pointer_t; - using base_type = std::remove_cv_t; - using this_ptr = detail::this_ptr_t; - using R = std::invoke_result_t; - - static_assert(std::is_member_function_pointer_v, - "Method must be a pointer-to-member function"); - static_assert(std::is_base_of_v, - "Receiver must inherit QObject"); - static_assert(std::is_base_of_v>, base_type>, - "Method not in object's class hierarchy"); - static_assert(std::is_invocable_v, - "Method cannot be invoked with provided arguments"); - static_assert(!std::is_reference_v, - "blocking_invoke does not support reference returns; return by value (or wrap) instead."); - - constexpr bool receiver_is_const = std::is_const_v; - constexpr bool method_is_const = std::is_const_v< - std::remove_reference_t< - detail::member_class_t - > - >; - static_assert(!receiver_is_const || method_is_const, - "Cannot invoke a non-const method on a const receiver"); - - if (QThread::currentThread() == raw_cv->thread()) { - if constexpr (std::is_void_v) { - std::invoke(method, static_cast(raw_cv), std::forward(args)...); - return; - } - else { - return std::invoke(method, static_cast(raw_cv), std::forward(args)...); - } - } - - QPointer guard{const_cast(raw_cv)}; - std::exception_ptr eptr; - - if constexpr (std::is_void_v) { - bool queued = QMetaObject::invokeMethod( - const_cast(raw_cv), - [guard, method, tup = std::make_tuple( - vnm::detail::decay_copy(std::forward(args))...), &eptr]() mutable - { - try { - auto* obj = guard.data(); - if (!obj) { - throw std::runtime_error( - "blocking_invoke: QObject was destroyed during call." - ); - } - std::apply( - [&](auto&&... u) { - std::invoke( - method, - static_cast(obj), - std::forward(u)... - ); - }, std::move(tup) - ); - } - catch (...) { - eptr = std::current_exception(); - } - }, - Qt::BlockingQueuedConnection); - - if (!queued) { - throw std::runtime_error( - "blocking_invoke: QMetaObject::invokeMethod failed to queue the call." - ); - } - if (eptr) { - std::rethrow_exception(eptr); - } - return; - } - else { - std::optional result; - bool queued = QMetaObject::invokeMethod( - const_cast(raw_cv), - [guard, method, tup = std::make_tuple( - vnm::detail::decay_copy(std::forward(args))...), &eptr, &result]() mutable - { - try { - auto* obj = guard.data(); - if (!obj) { - throw std::runtime_error( - "blocking_invoke: QObject was destroyed during call." - ); - } - result.emplace(std::apply( - [&](auto&&... u) -> R { - return std::invoke( - method, - static_cast(obj), - std::forward(u)... - ); - }, - std::move(tup)) - ); - } - catch (...) { - eptr = std::current_exception(); - } - }, - Qt::BlockingQueuedConnection - ); - - if (!queued) { - throw std::runtime_error( - "blocking_invoke: QMetaObject::invokeMethod failed to queue the call." - ); - } - if (eptr) { - std::rethrow_exception(eptr); - } - return *result; - } -} - - -/** - * @brief Nothrow version of blocking_invoke. - * @return For `void` methods, `true` on success, `false` on failure. - * @return For non-void `R` methods, `std::optional` with a value on success, empty on failure. - */ -template -VNM_NODISCARD auto try_blocking_invoke(ObjLike&& obj_like, Method method, Args&&... args) noexcept -{ - using R = std::invoke_result_t, Args...>; - static_assert(!std::is_reference_v, - "try_blocking_invoke does not support reference returns; use blocking_invoke instead."); - - try { - if constexpr (std::is_void_v) { - blocking_invoke(std::forward(obj_like), method, std::forward(args)...); - return true; - } - else { - return std::optional{ - blocking_invoke( - std::forward(obj_like), - method, - std::forward(args)... - ) - }; - } - } - catch (...) { - if constexpr (std::is_void_v) { - return false; - } - else { - return std::optional{}; - } - } -} - - -//================================================================================================// -// SIGNAL EMISSION -//================================================================================================// - -/** - * @brief Emits a signal directly if on the same thread, otherwise queues the emission. - * @return True if the signal was emitted directly or the emission was successfully queued. - * @return False if the emitter object was null at the time of the call. - */ -template -VNM_NODISCARD bool safe_emit(EmitterLike&& emitter_like, Signal signal, Args&&... args) -{ - auto* raw_cv = detail::to_raw(std::forward(emitter_like)); - if (!raw_cv) { - return false; - } - - using raw_type_cv = std::remove_pointer_t; - using base_type = std::remove_cv_t; - using this_ptr = detail::this_ptr_t; - - static_assert(std::is_member_function_pointer_v, - "Signal must be a pointer-to-member function"); - static_assert(std::is_base_of_v, - "Emitter must inherit QObject"); - static_assert(std::is_base_of_v>, base_type>, - "Signal is not in the emitter's class hierarchy"); - static_assert(std::is_invocable_v, - "Signal cannot be invoked with the provided arguments"); - static_assert(std::is_void_v>, - "Method passed must return void"); - static_assert(((std::is_copy_constructible_v> - || std::is_rvalue_reference_v) && ...), - "Each lvalue argument must be copy-constructible; pass rvalues (std::move) for move-only types."); - - constexpr bool emitter_is_const = std::is_const_v; - constexpr bool signal_is_const = std::is_const_v< - std::remove_reference_t< - detail::member_class_t - > - >; - static_assert(!emitter_is_const || signal_is_const, - "Cannot use a non-const signal/wrapper on a const emitter"); - - if (QThread::currentThread() == raw_cv->thread()) { - std::invoke( - signal, - static_cast(raw_cv), - std::forward(args)... - ); - return true; - } - - QPointer guard{const_cast(raw_cv)}; - return QMetaObject::invokeMethod( - const_cast(raw_cv), - [guard, signal, tup = std::make_tuple( - vnm::detail::decay_copy(std::forward(args))...)]() mutable - { - auto* obj = guard.data(); - if (!obj) { - return; - } - try { - std::apply( - [&](auto&&... u) { - std::invoke( - signal, - static_cast(obj), - std::forward(u)... - ); - }, - std::move(tup) - ); - } - catch (...) { -#ifdef QT_DEBUG - qCritical("safe_emit: Unhandled exception during queued emission. " - "Signals/wrappers must not throw."); - Q_ASSERT_X(false, "vnm::safe_emit", "Exception escaped target thread"); -#else - qWarning("safe_emit: Exception swallowed during queued emission. " - "Signals/wrappers should not throw."); -#endif - } - }, - Qt::QueuedConnection - ); -} - -/** - * @brief Posts a signal for asynchronous emission (always queued). "Fire-and-forget". - * @return True if the emission was successfully queued. - * @return False if the emitter was null or if argument capture failed. - */ -template -VNM_NODISCARD bool post_emit(EmitterLike&& emitter_like, Signal signal, Args&&... args) noexcept -{ - auto* raw_cv = detail::to_raw(std::forward(emitter_like)); - if (!raw_cv) { - return false; - } - - using raw_type_cv = std::remove_pointer_t; - using base_type = std::remove_cv_t; - using this_ptr = detail::this_ptr_t; - - static_assert(std::is_member_function_pointer_v, - "Signal must be a pointer-to-member function"); - static_assert(std::is_base_of_v, - "Emitter must inherit QObject"); - static_assert(std::is_base_of_v>, base_type>, - "Signal is not in the emitter's class hierarchy"); - static_assert(std::is_invocable_v, - "Signal cannot be invoked with the provided arguments"); - static_assert(std::is_void_v>, - "Method passed must return void"); - - constexpr bool emitter_is_const = std::is_const_v; - constexpr bool signal_is_const = std::is_const_v< - std::remove_reference_t< - detail::member_class_t - > - >; - static_assert(!emitter_is_const || signal_is_const, - "Cannot use a non-const signal/wrapper on a const emitter"); - static_assert(((std::is_copy_constructible_v> - || std::is_rvalue_reference_v) && ...), - "Each lvalue argument must be copy-constructible; pass rvalues (std::move) for move-only types."); - - try { - QPointer guard{const_cast(raw_cv)}; - return QMetaObject::invokeMethod( - const_cast(raw_cv), - [guard, signal, tup = std::make_tuple( - vnm::detail::decay_copy(std::forward(args))...)]() mutable - { - auto* obj = guard.data(); - if (!obj) { - return; - } - try { - std::apply( - [&](auto&&... u) { - std::invoke( - signal, - static_cast(obj), - std::forward(u)... - ); - }, - std::move(tup) - ); - } - catch (...) { - qWarning("post_emit: Exception swallowed during queued emission. " - "Signals/wrappers should not throw."); - } - }, - Qt::QueuedConnection - ); - } - catch (...) { - return false; - } -} - - -/** - * @brief Posts a functor to be executed on the emitter's thread for batch emissions. - * @return True if the batch functor was successfully queued. - * @return False if the emitter was null or the functor could not be captured. - */ -template -VNM_NODISCARD bool post_emit_batch(EmitterLike&& emitter_like, Functor&& f) noexcept -{ - auto* raw_cv = detail::to_raw(std::forward(emitter_like)); - if (!raw_cv) { - return false; - } - - using raw_type_cv = std::remove_pointer_t; - using base_type = std::remove_cv_t; - using functor_t = std::decay_t; - - static_assert(std::is_base_of_v, "Emitter must inherit QObject"); - - if constexpr (std::is_const_v) { - static_assert(std::is_invocable_v, - "For a const emitter, the functor must accept (const base_type*)."); - static_assert(std::is_invocable_r_v, - "The batch functor must return void."); - } - else { - static_assert(std::is_invocable_v, - "Functor must be invocable with (base_type*)."); - static_assert(std::is_invocable_r_v, - "The batch functor must return void."); - } - - static_assert(std::is_copy_constructible_v || std::is_rvalue_reference_v, - "Lvalue functors must be copy-constructible; pass rvalues (std::move) for move-only functors."); - - try { - QPointer guard{const_cast(raw_cv)}; - return QMetaObject::invokeMethod( - const_cast(raw_cv), - [guard, fn = std::forward(f)]() mutable { - auto* obj = guard.data(); - if (!obj) { - return; - } - try { - if constexpr (std::is_const_v) { - fn(static_cast(obj)); - } - else { - fn(obj); - } - } - catch (...) { - qWarning("post_emit_batch: Exception swallowed during queued emission. " - "The batch functor should not throw."); - } - }, - Qt::QueuedConnection - ); - } - catch (...) { - return false; - } -} - -// ============================================================================ -// CONVENIENCE OVERLOADS FOR LVALUES -// (Only enabled when the lvalue type itself is NOT a pointer. -// This avoids taking the address of a pointer (T* -> T**), which breaks -// the pointer-traits used above.) -// ============================================================================ - +// Convenience overload for lvalue objects (non-pointer). template>, int> = 0> VNM_NODISCARD bool post_invoke(Obj& o, Method m, Args&&... args) @@ -711,29 +159,7 @@ VNM_NODISCARD bool post_invoke(Obj& o, Method m, Args&&... args) return vnm::post_invoke(std::addressof(o), m, std::forward(args)...); } -template>, int> = 0> -decltype(auto) blocking_invoke(Obj& o, Method m, Args&&... args) -{ - using Ret = decltype(vnm::blocking_invoke(std::addressof(o), m, std::forward(args)...)); - if constexpr (std::is_void_v) { - vnm::blocking_invoke(std::addressof(o), m, std::forward(args)...); - } - else { - return vnm::blocking_invoke(std::addressof(o), m, std::forward(args)...); - } -} - -template>, int> = 0> -VNM_NODISCARD auto try_blocking_invoke(Obj& o, Method m, Args&&... args) noexcept - -> decltype(vnm::try_blocking_invoke(std::addressof(o), m, std::forward(args)...)) -{ - return vnm::try_blocking_invoke(std::addressof(o), m, std::forward(args)...); -} - -// --- Pointer lvalue overloads (handle raw T* variables safely) --------- - +// Convenience overload for lvalue pointers. template>, int> = 0> VNM_NODISCARD bool post_invoke(Ptr& p, Method m, Args&&... args) @@ -744,96 +170,4 @@ VNM_NODISCARD bool post_invoke(Ptr& p, Method m, Args&&... args) return vnm::post_invoke(*p, m, std::forward(args)...); } -template>, int> = 0> -decltype(auto) blocking_invoke(Ptr& p, Method m, Args&&... args) -{ - using Ret = decltype(vnm::blocking_invoke(*p, m, std::forward(args)...)); - if (!p) { - if constexpr (std::is_void_v) { - return; - } - else { - return Ret{}; - } - } - if constexpr (std::is_void_v) { - vnm::blocking_invoke(*p, m, std::forward(args)...); - } - else { - return vnm::blocking_invoke(*p, m, std::forward(args)...); - } -} - -template>, int> = 0> -VNM_NODISCARD auto try_blocking_invoke(Ptr& p, Method m, Args&&... args) noexcept - -> decltype(vnm::try_blocking_invoke(*p, m, std::forward(args)...)) -{ - using return_t = decltype(vnm::try_blocking_invoke(*p, m, std::forward(args)...)); - if (!p) { - if constexpr (std::is_same_v) { - return false; - } - else { - return return_t{}; - } - } - return vnm::try_blocking_invoke(*p, m, std::forward(args)...); -} - -// --- Emission --- - -template>, int> = 0> -VNM_NODISCARD bool safe_emit(Emitter& e, Signal s, Args&&... args) -{ - return vnm::safe_emit(std::addressof(e), s, std::forward(args)...); -} - -template>, int> = 0> -VNM_NODISCARD bool post_emit(Emitter& e, Signal s, Args&&... args) noexcept -{ - return vnm::post_emit(std::addressof(e), s, std::forward(args)...); -} - -template>, int> = 0> -VNM_NODISCARD bool post_emit_batch(Emitter& e, Functor&& f) noexcept -{ - return vnm::post_emit_batch(std::addressof(e), std::forward(f)); -} - -// --- Emission (raw pointer variables) ---------------------------------- - -template>, int> = 0> -VNM_NODISCARD bool safe_emit(Ptr& p, Signal s, Args&&... args) -{ - if (!p) { - return false; - } - return vnm::safe_emit(*p, s, std::forward(args)...); -} - -template>, int> = 0> -VNM_NODISCARD bool post_emit(Ptr& p, Signal s, Args&&... args) noexcept -{ - if (!p) { - return false; - } - return vnm::post_emit(*p, s, std::forward(args)...); -} - -template>, int> = 0> -VNM_NODISCARD bool post_emit_batch(Ptr& p, Functor&& f) noexcept { - if (!p) { - return false; - } - return vnm::post_emit_batch(*p, std::forward(f)); -} - } // namespace vnm diff --git a/src/core/asset_loader.cpp b/src/core/asset_loader.cpp index 4bfe30b..8a3593f 100644 --- a/src/core/asset_loader.cpp +++ b/src/core/asset_loader.cpp @@ -27,11 +27,6 @@ void Asset_loader::set_override_directory(std::string_view path) m_override_dir = std::string(path); } -std::string_view Asset_loader::override_directory() const noexcept -{ - return m_override_dir; -} - void Asset_loader::register_embedded(std::string_view name, std::string_view data) { m_embedded[std::string(name)] = data; diff --git a/src/core/chrome_renderer.cpp b/src/core/chrome_renderer.cpp index e2a7b60..cdd2458 100644 --- a/src/core/chrome_renderer.cpp +++ b/src/core/chrome_renderer.cpp @@ -32,6 +32,23 @@ bool is_integer_multiple(double parent, double child) return std::abs(ratio - rounded) <= tol; } +float compute_grid_alpha(float spacing_px, double cell_span_min, double fade_den) +{ + const double fade = (double(spacing_px) - cell_span_min) / fade_den; + return static_cast(std::clamp(fade, 0.0, 1.0) * k_grid_line_alpha_base); +} + +void append_grid_level(grid_layer_params_t& levels, float spacing_px, float start_px, + double cell_span_min, double fade_den) +{ + levels.spacing_px[levels.count] = spacing_px; + levels.start_px[levels.count] = start_px; + const float a = compute_grid_alpha(spacing_px, cell_span_min, fade_den); + levels.alpha[levels.count] = a; + levels.thickness_px[levels.count] = 0.6f + 0.6f * (a / k_grid_line_alpha_base); + ++levels.count; +} + grid_layer_params_t build_time_grid( double t_min, double t_max, @@ -47,10 +64,6 @@ grid_layer_params_t build_time_grid( const double cell_span_min = font_px * k_cell_span_min_factor; const double fade_den = std::max(1e-6, font_px * (k_cell_span_max_factor - k_cell_span_min_factor)); - const auto compute_alpha = [&](float spacing_px) -> float { - const double fade = (double(spacing_px) - cell_span_min) / fade_den; - return static_cast(std::clamp(fade, 0.0, 1.0) * k_grid_line_alpha_base); - }; const double px_per_unit = width_px / range; const auto steps = build_time_steps_covering(range); @@ -77,12 +90,8 @@ grid_layer_params_t build_time_grid( continue; } const double shift_units = get_shift(step, t_min); - levels.spacing_px[levels.count] = spacing_px; - levels.start_px[levels.count] = static_cast(shift_units * px_per_unit); - const float a = compute_alpha(spacing_px); - levels.alpha[levels.count] = a; - levels.thickness_px[levels.count] = 0.6f + 0.6f * (a / k_grid_line_alpha_base); - ++levels.count; + append_grid_level(levels, spacing_px, + static_cast(shift_units * px_per_unit), cell_span_min, fade_den); last_step = step; } @@ -133,24 +142,14 @@ grid_layer_params_t Chrome_renderer::calculate_grid_params( } const double cell_span_min = font_px * k_cell_span_min_factor; - const double cell_span_max = font_px * k_cell_span_max_factor; - const double fade_den = std::max(1e-6, cell_span_max - cell_span_min); + const double fade_den = std::max(1e-6, font_px * (k_cell_span_max_factor - k_cell_span_min_factor)); const double px_per_unit = pixel_span / range; - const auto compute_alpha = [&](float spacing_px) -> float { - const double fade = (double(spacing_px) - cell_span_min) / fade_den; - return static_cast(std::clamp(fade, 0.0, 1.0) * k_grid_line_alpha_base); - }; - while (levels.count < grid_layer_params_t::k_max_levels && step * px_per_unit >= cell_span_min) { const float spacing_px = static_cast(step * px_per_unit); const double shift_units = get_shift(step, min); - levels.spacing_px[levels.count] = spacing_px; - levels.start_px[levels.count] = static_cast(pixel_span - shift_units * px_per_unit); - const float a = compute_alpha(spacing_px); - levels.alpha[levels.count] = a; - levels.thickness_px[levels.count] = 0.6f + 0.6f * (a / k_grid_line_alpha_base); - ++levels.count; + append_grid_level(levels, spacing_px, + static_cast(pixel_span - shift_units * px_per_unit), cell_span_min, fade_den); step /= om_div[circular_index(om_div, --idx)]; } @@ -168,10 +167,10 @@ void Chrome_renderer::render_grid_and_backgrounds( "renderer.frame.chrome.grid_and_backgrounds"); // Skip GL calls if configured (for pure CPU profiling) - const bool skip_gl = ctx.config && ctx.config->skip_gl_calls; + const bool skip_gl = ctx.skip_gl; const auto& pl = ctx.layout; - const bool dark_mode = ctx.config ? ctx.config->dark_mode : false; + const bool dark_mode = ctx.dark_mode; const Color_palette palette = dark_mode ? Color_palette::dark() : Color_palette::light(); const glm::vec4 h_label_color = palette.h_label_background; @@ -252,30 +251,13 @@ void Chrome_renderer::render_grid_and_backgrounds( return {alpha, thick}; }; - const auto build_vertical_tick_levels = [&](const std::vector& labels, const grid_layer_params_t& main_levels) { - grid_layer_params_t ticks; - for (const auto& label : labels) { - if (ticks.count >= grid_layer_params_t::k_max_levels) { - break; - } - const float pos = label.y; - const auto props = match_level_properties(pos, main_levels); - ticks.spacing_px[ticks.count] = 1e6f; - ticks.start_px[ticks.count] = pos; - ticks.alpha[ticks.count] = props.first; - ticks.thickness_px[ticks.count] = props.second; - ++ticks.count; - } - return ticks; - }; - - const auto build_horizontal_tick_levels = [&](const std::vector& labels, const grid_layer_params_t& main_levels) { + const auto build_tick_levels = [&](auto&& get_pos, const auto& labels, const grid_layer_params_t& main_levels) { grid_layer_params_t ticks; for (const auto& label : labels) { if (ticks.count >= grid_layer_params_t::k_max_levels) { break; } - const float pos = label.position.x; + const float pos = get_pos(label); const auto props = match_level_properties(pos, main_levels); ticks.spacing_px[ticks.count] = 1e6f; ticks.start_px[ticks.count] = pos; @@ -287,8 +269,10 @@ void Chrome_renderer::render_grid_and_backgrounds( }; grid_layer_params_t empty_levels; - const grid_layer_params_t vertical_tick_levels = build_vertical_tick_levels(pl.v_labels, vertical_levels); - const grid_layer_params_t horizontal_tick_levels = build_horizontal_tick_levels(pl.h_labels, horizontal_levels); + const grid_layer_params_t vertical_tick_levels = build_tick_levels( + [](const v_label_t& l) { return l.y; }, pl.v_labels, vertical_levels); + const grid_layer_params_t horizontal_tick_levels = build_tick_levels( + [](const h_label_t& l) { return l.position.x; }, pl.h_labels, horizontal_levels); const grid_layer_params_t vertical_tick_levels_gl = flip_grid_levels_y(vertical_tick_levels, main_size.y); if (!skip_gl && pl.v_bar_width > 0.5 && vertical_tick_levels_gl.count > 0) { @@ -310,7 +294,7 @@ void Chrome_renderer::render_zero_line( const frame_context_t& ctx, Primitive_renderer& prims) { - const bool skip_gl = ctx.config && ctx.config->skip_gl_calls; + const bool skip_gl = ctx.skip_gl; if (skip_gl) { return; } @@ -328,7 +312,7 @@ void Chrome_renderer::render_zero_line( return; } - const bool dark_mode = ctx.config ? ctx.config->dark_mode : false; + const bool dark_mode = ctx.dark_mode; const Color_palette palette = dark_mode ? Color_palette::dark() : Color_palette::light(); const glm::vec4 color = palette.grid_line; @@ -358,7 +342,7 @@ void Chrome_renderer::render_preview_overlay( "renderer.frame.chrome.preview_overlay"); // Skip GL calls if configured (for pure CPU profiling) - const bool skip_gl = ctx.config && ctx.config->skip_gl_calls; + const bool skip_gl = ctx.skip_gl; if (ctx.adjusted_preview_height <= 0.) { return; @@ -369,7 +353,7 @@ void Chrome_renderer::render_preview_overlay( return; } - const bool dark_mode = ctx.config ? ctx.config->dark_mode : false; + const bool dark_mode = ctx.dark_mode; const Color_palette palette = dark_mode ? Color_palette::dark() : Color_palette::light(); const glm::vec4 cover_color = palette.preview_cover; const glm::vec4 cover_color2 = palette.preview_cover_secondary; diff --git a/src/core/layout_calculator.cpp b/src/core/layout_calculator.cpp index 2bce39d..1144743 100644 --- a/src/core/layout_calculator.cpp +++ b/src/core/layout_calculator.cpp @@ -1084,7 +1084,6 @@ Layout_calculator::result_t Layout_calculator::calculate(const parameters_t& par "renderer.frame.calculate_layout.impl.cache_miss.pass1.horizontal_axis.finalize"); if (any_level) { res.h_labels_subsecond = any_subsec; - res.horizontal_finest_step = finest_step; } res.horizontal_seed_index = start_si; diff --git a/src/core/platform_paths.cpp b/src/core/platform_paths.cpp index 7a7c8bc..0ad37e5 100644 --- a/src/core/platform_paths.cpp +++ b/src/core/platform_paths.cpp @@ -11,7 +11,7 @@ namespace vnm::plot { namespace { -std::string s_app_name = "vnm_plot"; +constexpr const char* s_app_name = "vnm_plot"; #ifdef _WIN32 @@ -28,15 +28,7 @@ std::filesystem::path get_known_folder(int folder_id) return {}; } -#elif defined(__APPLE__) - -std::filesystem::path get_home_directory() -{ - const char* home = std::getenv("HOME"); - return home ? std::filesystem::path(home) : std::filesystem::path(); -} - -#else // Linux/Unix +#else // macOS / Linux / Unix std::filesystem::path get_home_directory() { @@ -44,6 +36,7 @@ std::filesystem::path get_home_directory() return home ? std::filesystem::path(home) : std::filesystem::path(); } +#if !defined(__APPLE__) std::filesystem::path get_xdg_path(const char* env_var, const char* default_subpath) { const char* xdg_path = std::getenv(env_var); @@ -53,9 +46,10 @@ std::filesystem::path get_xdg_path(const char* env_var, const char* default_subp auto home = get_home_directory(); return home.empty() ? std::filesystem::path() : home / default_subpath; } - #endif +#endif // _WIN32 + std::filesystem::path ensure_directory(const std::filesystem::path& dir) { if (dir.empty()) { @@ -124,14 +118,4 @@ std::filesystem::path get_data_directory() #endif } -void set_app_name(const std::string& name) -{ - s_app_name = name; -} - -const std::string& get_app_name() -{ - return s_app_name; -} - } // namespace vnm::plot diff --git a/src/core/platform_paths.h b/src/core/platform_paths.h index 8a64eb4..49d9670 100644 --- a/src/core/platform_paths.h +++ b/src/core/platform_paths.h @@ -32,10 +32,4 @@ namespace vnm::plot { // - Linux: $XDG_DATA_HOME/vnm_plot or ~/.local/share/vnm_plot [[nodiscard]] std::filesystem::path get_data_directory(); -// Set the application name used in directory paths (default: "vnm_plot") -void set_app_name(const std::string& name); - -// Get the current application name -[[nodiscard]] const std::string& get_app_name(); - } // namespace vnm::plot diff --git a/src/core/plot_core.cpp b/src/core/plot_core.cpp index 0ed64c1..9c73ed3 100644 --- a/src/core/plot_core.cpp +++ b/src/core/plot_core.cpp @@ -271,6 +271,8 @@ void Plot_core::render( ctx.adjusted_reserved_height = reserved_height; ctx.adjusted_preview_height = preview_height; ctx.show_info = params.show_info; + ctx.skip_gl = config && config->skip_gl_calls; + ctx.dark_mode = config ? config->dark_mode : false; ctx.config = config; m_impl->chrome.render_grid_and_backgrounds(ctx, m_impl->primitives); diff --git a/src/core/series_renderer.cpp b/src/core/series_renderer.cpp index c5af8af..f065c06 100644 --- a/src/core/series_renderer.cpp +++ b/src/core/series_renderer.cpp @@ -453,7 +453,6 @@ Series_renderer::view_render_result_t Series_renderer::process_view( } if (!snapshot_result || !snapshot_result.snapshot || snapshot_result.snapshot.count == 0) { - ++m_metrics.snapshot_failures; if (try_stale_fallback(result)) break; if (applied_level > 0) { target_level = applied_level - 1; continue; } break; @@ -586,7 +585,7 @@ Series_renderer::view_render_result_t Series_renderer::process_view( const double base_pps = (base_samples > 0) ? width_px / static_cast(base_samples) : 0.0; - const std::size_t desired_level = choose_lod_level(scales, applied_level, base_pps); + const std::size_t desired_level = choose_lod_level(scales, base_pps); if (desired_level != applied_level) { if (!was_tried(desired_level)) { target_level = desired_level; @@ -619,8 +618,6 @@ Series_renderer::view_render_result_t Series_renderer::process_view( const std::size_t alloc_size = needed_bytes + needed_bytes / 4; glBufferData(GL_ARRAY_BUFFER, static_cast(alloc_size), nullptr, GL_DYNAMIC_DRAW); view_state.last_ring_size = alloc_size; - ++m_metrics.vbo_reallocations; - m_metrics.bytes_allocated += alloc_size; } if (!snapshot.is_segmented()) { glBufferSubData(GL_ARRAY_BUFFER, 0, @@ -639,7 +636,6 @@ Series_renderer::view_render_result_t Series_renderer::process_view( static_cast(bytes2), snapshot.data2); } } - m_metrics.bytes_uploaded += snapshot.count * snapshot.stride; } if (needs_hold_upload && !skip_gl) { const void* source_sample = snapshot.at(snapshot.count - 1); @@ -655,7 +651,6 @@ Series_renderer::view_render_result_t Series_renderer::process_view( static_cast(snapshot.count * snapshot.stride), static_cast(snapshot.stride), hold_sample.data()); - m_metrics.bytes_uploaded += snapshot.stride; } } if (must_upload) { @@ -713,7 +708,7 @@ void Series_renderer::set_common_uniforms( glUniform1i(program.uniform_location("snap_to_pixels"), snap ? 1 : 0); // Zero-axis color (same as grid lines) - const bool dark_mode = ctx.config ? ctx.config->dark_mode : false; + const bool dark_mode = ctx.dark_mode; const Color_palette palette = dark_mode ? Color_palette::dark() : Color_palette::light(); glUniform4fv(program.uniform_location("zero_axis_color"), 1, glm::value_ptr(palette.grid_line)); } @@ -757,9 +752,9 @@ void Series_renderer::render( VNM_PLOT_PROFILE_SCOPE(profiler, "renderer.frame.execute_passes.render_data_series"); // Skip all GL calls if configured (for pure CPU profiling) - const bool skip_gl = ctx.config && ctx.config->skip_gl_calls; + const bool skip_gl = ctx.skip_gl; - const bool dark_mode = ctx.config ? ctx.config->dark_mode : false; + const bool dark_mode = ctx.dark_mode; const float line_width = ctx.config ? static_cast(ctx.config->line_width_px) : 1.0f; const float area_fill_alpha = ctx.config ? static_cast(ctx.config->area_fill_alpha) : 0.3f; const auto to_gl_scissor_y = [&](double top, double height) -> GLint { diff --git a/src/core/text_renderer.cpp b/src/core/text_renderer.cpp index 0c2beb9..ce3f510 100644 --- a/src/core/text_renderer.cpp +++ b/src/core/text_renderer.cpp @@ -118,7 +118,7 @@ bool Text_renderer::render(const frame_context_t& ctx, bool fade_v_labels, bool } // Skip GL calls if configured (for pure CPU profiling) - const bool skip_gl = ctx.config && ctx.config->skip_gl_calls; + const bool skip_gl = ctx.skip_gl; if (!skip_gl) { glEnable(GL_BLEND); @@ -138,11 +138,11 @@ bool Text_renderer::render(const frame_context_t& ctx, bool fade_v_labels, bool bool Text_renderer::render_axis_labels(const frame_context_t& ctx, bool fade_labels) { const auto& pl = ctx.layout; - const bool dark_mode = ctx.config ? ctx.config->dark_mode : false; + const bool dark_mode = ctx.dark_mode; const glm::vec4 font_color = dark_mode ? glm::vec4(1.f, 1.f, 1.f, 1.f) : glm::vec4(0.f, 0.f, 0.f, 1.f); // Skip GL calls if configured (for pure CPU profiling) - const bool skip_gl = ctx.config && ctx.config->skip_gl_calls; + const bool skip_gl = ctx.skip_gl; const float right_edge_x = static_cast(pl.usable_width + pl.v_bar_width - k_v_label_horizontal_padding_px); const float min_x = static_cast(pl.usable_width + k_text_margin_px); @@ -232,12 +232,12 @@ bool Text_renderer::render_axis_labels(const frame_context_t& ctx, bool fade_lab bool Text_renderer::render_info_overlay(const frame_context_t& ctx, bool fade_labels) { const auto& pl = ctx.layout; - const bool dark_mode = ctx.config ? ctx.config->dark_mode : false; + const bool dark_mode = ctx.dark_mode; const glm::vec4 font_color = dark_mode ? glm::vec4(1.f, 1.f, 1.f, 1.f) : glm::vec4(0.f, 0.f, 0.f, 1.f); const double t_span = ctx.t1 - ctx.t0; // Skip GL calls if configured (for pure CPU profiling) - const bool skip_gl = ctx.config && ctx.config->skip_gl_calls; + const bool skip_gl = ctx.skip_gl; const auto draw_label = [&](double t, const label_fade_state_t& state) { if (!(t_span > 0.0) || !(pl.usable_width > 0.0)) { diff --git a/src/core/utf8_utils.cpp b/src/core/utf8_utils.cpp index 35f0d30..f6d1059 100644 --- a/src/core/utf8_utils.cpp +++ b/src/core/utf8_utils.cpp @@ -1,7 +1,5 @@ #include "utf8_utils.h" -#include - namespace vnm::plot { namespace { @@ -14,8 +12,9 @@ constexpr bool is_continuation(unsigned char c) noexcept return (c & 0xC0) == 0x80; } -} // anonymous namespace - +// Decode a single UTF-8 code point from a string. +// Returns the code point and advances the iterator. +// Returns 0xFFFD (replacement character) on invalid sequences. char32_t utf8_decode_one(const char*& it, const char* end) noexcept { if (it >= end) { @@ -86,21 +85,9 @@ char32_t utf8_decode_one(const char*& it, const char* end) noexcept return cp; } -std::vector utf8_to_codepoints(std::string_view utf8) -{ - std::vector result; - result.reserve(utf8.size()); // Upper bound estimate - - const char* it = utf8.data(); - const char* end = it + utf8.size(); - - while (it < end) { - result.push_back(utf8_decode_one(it, end)); - } - - return result; -} - +// Convert a single Unicode code point to UTF-8. +// Returns the number of bytes written (1-4), or 0 on invalid code point. +// Output buffer must have at least 4 bytes. size_t codepoint_to_utf8(char32_t cp, char* out) noexcept { auto* p = reinterpret_cast(out); @@ -135,118 +122,36 @@ size_t codepoint_to_utf8(char32_t cp, char* out) noexcept return 0; // Invalid code point } -std::string codepoints_to_utf8(const std::vector& codepoints) -{ - std::string result; - result.reserve(codepoints.size() * 2); // Rough estimate - - char buf[4]; - for (char32_t cp : codepoints) { - size_t len = codepoint_to_utf8(cp, buf); - if (len > 0) { - result.append(buf, len); - } - } - - return result; -} +} // anonymous namespace -size_t utf8_length(std::string_view utf8) noexcept +std::vector utf8_to_codepoints(std::string_view utf8) { - size_t count = 0; + std::vector result; + result.reserve(utf8.size()); // Upper bound estimate + const char* it = utf8.data(); const char* end = it + utf8.size(); while (it < end) { - (void)utf8_decode_one(it, end); - ++count; - } - - return count; -} - -bool is_printable(char32_t cp) noexcept -{ - // Basic printable check (covers ASCII and common Unicode) - if (cp < 0x20) { - return false; // Control characters - } - if (cp == 0x7F) { - return false; // DEL - } - if (cp >= 0x80 && cp < 0xA0) { - return false; // C1 control characters - } - if (cp > 0x10FFFF) { - return false; // Beyond Unicode - } - return true; -} - -bool is_whitespace(char32_t cp) noexcept -{ - switch (cp) { - case 0x0009: // Tab - case 0x000A: // Line feed - case 0x000B: // Vertical tab - case 0x000C: // Form feed - case 0x000D: // Carriage return - case 0x0020: // Space - case 0x0085: // Next line - case 0x00A0: // Non-breaking space - case 0x1680: // Ogham space mark - case 0x2000: case 0x2001: case 0x2002: case 0x2003: - case 0x2004: case 0x2005: case 0x2006: case 0x2007: - case 0x2008: case 0x2009: case 0x200A: // Various spaces - case 0x2028: // Line separator - case 0x2029: // Paragraph separator - case 0x202F: // Narrow no-break space - case 0x205F: // Medium mathematical space - case 0x3000: // Ideographic space - return true; - default: - return false; - } -} - -bool is_digit(char32_t cp) noexcept -{ - return cp >= '0' && cp <= '9'; -} - -std::string_view trim(std::string_view str) noexcept -{ - // Trim ASCII whitespace from both ends - const char* begin = str.data(); - const char* end = begin + str.size(); - - while (begin < end && (*begin == ' ' || *begin == '\t' || - *begin == '\n' || *begin == '\r')) { - ++begin; - } - - while (end > begin && (*(end - 1) == ' ' || *(end - 1) == '\t' || - *(end - 1) == '\n' || *(end - 1) == '\r')) { - --end; + result.push_back(utf8_decode_one(it, end)); } - return std::string_view(begin, static_cast(end - begin)); + return result; } -std::vector split(std::string_view str, char delim) +std::string codepoints_to_utf8(const std::vector& codepoints) { - std::vector result; - - size_t start = 0; - size_t end = str.find(delim); + std::string result; + result.reserve(codepoints.size() * 2); // Rough estimate - while (end != std::string_view::npos) { - result.push_back(str.substr(start, end - start)); - start = end + 1; - end = str.find(delim, start); + char buf[4]; + for (char32_t cp : codepoints) { + size_t len = codepoint_to_utf8(cp, buf); + if (len > 0) { + result.append(buf, len); + } } - result.push_back(str.substr(start)); return result; } diff --git a/src/core/utf8_utils.h b/src/core/utf8_utils.h index 126f2f6..34f1ab3 100644 --- a/src/core/utf8_utils.h +++ b/src/core/utf8_utils.h @@ -10,65 +10,13 @@ namespace vnm::plot { -// ----------------------------------------------------------------------------- -// UTF-8 to Unicode Code Point Conversion -// ----------------------------------------------------------------------------- - -// Decode a single UTF-8 code point from a string. -// Returns the code point and advances the iterator. -// Returns 0xFFFD (replacement character) on invalid sequences. -[[nodiscard]] -char32_t utf8_decode_one(const char*& it, const char* end) noexcept; - // Convert a UTF-8 string to a vector of Unicode code points (UCS-4). // Replaces invalid sequences with U+FFFD. [[nodiscard]] std::vector utf8_to_codepoints(std::string_view utf8); -// Convert a single Unicode code point to UTF-8. -// Returns the number of bytes written (1-4), or 0 on invalid code point. -// Output buffer must have at least 4 bytes. -size_t codepoint_to_utf8(char32_t cp, char* out) noexcept; - // Convert a vector of code points back to UTF-8. [[nodiscard]] std::string codepoints_to_utf8(const std::vector& codepoints); -// ----------------------------------------------------------------------------- -// String Length Utilities -// ----------------------------------------------------------------------------- - -// Count the number of Unicode code points in a UTF-8 string. -// Invalid sequences count as one code point each. -[[nodiscard]] -size_t utf8_length(std::string_view utf8) noexcept; - -// ----------------------------------------------------------------------------- -// Character Classification -// ----------------------------------------------------------------------------- - -// Check if a code point is a printable character -[[nodiscard]] -bool is_printable(char32_t cp) noexcept; - -// Check if a code point is whitespace -[[nodiscard]] -bool is_whitespace(char32_t cp) noexcept; - -// Check if a code point is a digit (0-9) -[[nodiscard]] -bool is_digit(char32_t cp) noexcept; - -// ----------------------------------------------------------------------------- -// Simple String Operations (ASCII-focused, UTF-8 safe) -// ----------------------------------------------------------------------------- - -// Trim whitespace from both ends of a UTF-8 string -[[nodiscard]] -std::string_view trim(std::string_view str) noexcept; - -// Split a string by a delimiter character (ASCII only) -[[nodiscard]] -std::vector split(std::string_view str, char delim); - } // namespace vnm::plot diff --git a/src/qt/plot_renderer.cpp b/src/qt/plot_renderer.cpp index 34a96de..7f4ac42 100644 --- a/src/qt/plot_renderer.cpp +++ b/src/qt/plot_renderer.cpp @@ -564,7 +564,7 @@ std::pair compute_visible_v_range( const double base_pps = (base_samples > 0 && width_px > 0.0) ? width_px / static_cast(base_samples) : 0.0; - const std::size_t desired_level = choose_lod_level(scales, 0, base_pps); + const std::size_t desired_level = choose_lod_level(scales, base_pps); std::size_t applied_level = desired_level; data_snapshot_t snapshot = view.source->snapshot(applied_level); @@ -750,12 +750,9 @@ struct Plot_renderer::impl_t Series_renderer series; view_state_t view; - int init_failed_status = 0; int last_font_px = 0; int viewport_width = 0; int viewport_height = 0; - int last_opengl_status = std::numeric_limits::min(); - int last_hlabels_subsecond = -1; std::uint32_t assets_revision = 0; std::uint64_t last_full_render_signature = 0ULL; @@ -764,9 +761,6 @@ struct Plot_renderer::impl_t bool assets_registered = false; bool initialized = false; bool init_failed = false; - bool methods_checked = false; - bool has_opengl_status_method = false; - bool has_hlabels_subsecond_method = false; bool has_last_full_render_signature = false; const frame_layout_result_t& calculate_frame_layout( @@ -993,15 +987,6 @@ void Plot_renderer::synchronize(QQuickFramebufferObject* fbo_item) return; } - if (!m_impl->methods_checked) { - const QMetaObject* meta = widget->metaObject(); - m_impl->has_opengl_status_method = - meta && meta->indexOfMethod("set_opengl_status_from_renderer(int)") >= 0; - m_impl->has_hlabels_subsecond_method = - meta && meta->indexOfMethod("set_hlabels_subsecond_from_renderer(bool)") >= 0; - m_impl->methods_checked = true; - } - // Copy configuration { std::shared_lock lock(widget->m_config_mutex); @@ -1036,37 +1021,6 @@ void Plot_renderer::synchronize(QQuickFramebufferObject* fbo_item) void Plot_renderer::render() { - auto notify_opengl_status = [&](int status) { - if (!m_impl->owner || !m_impl->has_opengl_status_method) { - return; - } - if (m_impl->last_opengl_status == status) { - return; - } - m_impl->last_opengl_status = status; - QMetaObject::invokeMethod( - const_cast(m_impl->owner), - "set_opengl_status_from_renderer", - Qt::QueuedConnection, - Q_ARG(int, status)); - }; - - auto notify_hlabels_subsecond = [&](bool subsecond) { - if (!m_impl->owner || !m_impl->has_hlabels_subsecond_method) { - return; - } - const int state = subsecond ? 1 : 0; - if (m_impl->last_hlabels_subsecond == state) { - return; - } - m_impl->last_hlabels_subsecond = state; - QMetaObject::invokeMethod( - const_cast(m_impl->owner), - "set_hlabels_subsecond_from_renderer", - Qt::QueuedConnection, - Q_ARG(bool, subsecond)); - }; - const Plot_config* config = &m_impl->snapshot.config; vnm::plot::Profiler* profiler = config->profiler.get(); const bool allow_renderer_self_scheduling = config->allow_renderer_self_scheduling; @@ -1076,9 +1030,6 @@ void Plot_renderer::render() } if (m_impl->init_failed) { - if (m_impl->init_failed_status != 0) { - notify_opengl_status(m_impl->init_failed_status); - } return; } @@ -1102,8 +1053,6 @@ void Plot_renderer::render() if (!has_required_opengl()) { m_impl->init_failed = true; m_impl->initialized = true; - m_impl->init_failed_status = -2; - notify_opengl_status(-2); return; } @@ -1114,7 +1063,6 @@ void Plot_renderer::render() register_assets_if_needed(); if (!m_impl->primitives.initialize(m_impl->asset_loader)) { - notify_opengl_status(-3); return; } m_impl->series.initialize(m_impl->asset_loader); @@ -1126,7 +1074,6 @@ void Plot_renderer::render() m_impl->text = std::make_unique(&m_impl->fonts); m_impl->initialized = true; - notify_opengl_status(1); } VNM_PLOT_PROFILE_SCOPE(profiler, "renderer"); @@ -1276,10 +1223,10 @@ void Plot_renderer::render() m_impl->view.v_range_cache, auto_mode); if (!cache_invalid && preview_enabled) { - cache_invalid = !validate_preview_range_cache_sequences( + cache_invalid = !validate_range_cache_sequences( series_snapshot, m_impl->view.preview_v_range_cache, - auto_mode); + auto_mode, /*preview=*/true); } } @@ -1552,7 +1499,6 @@ void Plot_renderer::render() }(); { VNM_PLOT_PROFILE_SCOPE(profiler, "renderer.frame.layout_finalize"); - notify_hlabels_subsecond(frame_layout.h_labels_subsecond); m_impl->update_seed_history(v_span, t_span, frame_layout); } @@ -1576,12 +1522,14 @@ void Plot_renderer::render() ctx.adjusted_reserved_height = m_impl->snapshot.adjusted_reserved_height; ctx.adjusted_preview_height = m_impl->snapshot.adjusted_preview_height; ctx.show_info = m_impl->snapshot.show_info; + ctx.skip_gl = config && config->skip_gl_calls; + ctx.dark_mode = config ? config->dark_mode : false; ctx.config = config; return ctx; }(); // Clear to transparent - let QML provide the background color (matches Lumis behavior) - const bool dark_mode = config ? config->dark_mode : false; + const bool dark_mode = core_ctx.dark_mode; const bool clear_to_transparent = config ? config->clear_to_transparent : false; const Color_palette palette = dark_mode ? Color_palette::dark() : Color_palette::light(); diff --git a/tests/test_cache_invalidation.cpp b/tests/test_cache_invalidation.cpp index f455e40..e7492f0 100644 --- a/tests/test_cache_invalidation.cpp +++ b/tests/test_cache_invalidation.cpp @@ -144,6 +144,8 @@ frame_context_t make_context(const frame_layout_result_t& layout, Plot_config& c ctx.t_available_max = 399.0; ctx.win_w = 100; ctx.win_h = 100; + ctx.skip_gl = config.skip_gl_calls; + ctx.dark_mode = config.dark_mode; ctx.config = &config; return ctx; } @@ -431,7 +433,7 @@ bool test_preview_matches_main_helpers() return true; } -bool test_validate_preview_range_cache_sequences() +bool test_validate_range_cache_sequences() { auto main_source = std::make_shared(); main_source->samples.resize(1); @@ -458,17 +460,17 @@ bool test_validate_preview_range_cache_sequences() cache.lods[0].valid = true; cache.lods[0].sequence = preview_source->current_sequence(0); - const bool valid = validate_preview_range_cache_sequences( + const bool valid = validate_range_cache_sequences( series_map, cache_map, - Auto_v_range_mode::GLOBAL); + Auto_v_range_mode::GLOBAL, /*preview=*/true); TEST_ASSERT(valid, "expected preview cache to be valid when sequences match"); preview_source->current_sequence_value = cache.lods[0].sequence + 1; - const bool valid_after = validate_preview_range_cache_sequences( + const bool valid_after = validate_range_cache_sequences( series_map, cache_map, - Auto_v_range_mode::GLOBAL); + Auto_v_range_mode::GLOBAL, /*preview=*/true); TEST_ASSERT(!valid_after, "expected preview cache invalidation on sequence change"); return true; @@ -492,7 +494,7 @@ int main() RUN_TEST(test_validate_range_cache_skips_missing_accessors); RUN_TEST(test_validate_range_cache_skips_disabled_series); RUN_TEST(test_preview_matches_main_helpers); - RUN_TEST(test_validate_preview_range_cache_sequences); + RUN_TEST(test_validate_range_cache_sequences); std::cout << "Results: " << passed << " passed, " << failed << " failed" << std::endl; diff --git a/tests/test_snapshot_caching.cpp b/tests/test_snapshot_caching.cpp index 6428982..5260568 100644 --- a/tests/test_snapshot_caching.cpp +++ b/tests/test_snapshot_caching.cpp @@ -137,6 +137,8 @@ frame_context_t make_context(const frame_layout_result_t& layout, Plot_config& c ctx.t_available_max = 10.0; ctx.win_w = 200; ctx.win_h = 120; + ctx.skip_gl = config.skip_gl_calls; + ctx.dark_mode = config.dark_mode; ctx.config = &config; return ctx; }