diff --git a/Error_handling_specification.md b/Error_handling_specification.md index d05215f..950edae 100644 --- a/Error_handling_specification.md +++ b/Error_handling_specification.md @@ -2,7 +2,7 @@ ## 1. 已知结果处理规则 -属于 API 契约允许出现、调用方能够明确处理的情况,使用返回码表达,例如 `Xxx_Result`、`Xxx_Status`、`Xxx_Error`。 +属于 API 契约允许出现、调用方能够明确处理的情况,使用返回码表达。 返回码不等于错误码,`timeout`、`cancelled`、`not_ready`、`no_change` 等都可以是正常结果。 @@ -12,6 +12,67 @@ 不按“内部/外部”区分。 +### 返回码定义规则 + +**每个存在返回码的函数必须拥有自己独立的返回码枚举,不得在一个类或模块中定义大而通用的公共返回码枚举供多个函数混用。** + +例如: + +```cpp +enum class Request_Frame_Result { + not_ready, + cancelled +}; + +std::expected request_frame(); +``` + +```cpp +enum class Resize_Result { + no_change, + unsupported +}; + +Resize_Result resize(Size size); +``` + +禁止: + +```cpp +enum class Scene_Result { + not_ready, + cancelled, + no_change, + unsupported, + no_pending_frame, + ... +}; +``` + +然后由多个函数共同返回 `Scene_Result`。 + +规则如下: + +* 有正常返回值,同时存在返回码: + + ```cpp + std::expected + ``` +* 没有额外正常返回值,返回码本身即可完整表达结果: + + ```cpp + Xxx_Result + ``` +* 没有已知返回码: + + ```cpp + T + void + ``` +* 返回码类型必须对应具体函数的契约,函数之间不得为了减少枚举数量而合并返回码。 + +这样可以直接从函数签名确定该函数所有需要调用方处理的已知结果。 + ## 2. 未知失败处理规则 不属于正常结果空间,或者当前调用路径没有可靠恢复方式的情况,统一视为 **Unknown Failure**。 @@ -47,19 +108,17 @@ exception 第三方库自身使用返回码还是异常,不决定本系统的处理方式。 -接入第三方库后必须重新按照本系统规则分类: - ```text 第三方返回结果 -> 本系统可预料、可处理 - -> 转换为本系统返回码 + -> 转换为当前函数自己的返回码 -> 本系统没有可靠恢复方式 -> 转换为 Unknown Failure - -> 交给当前 Unknown Failure Policy + -> 交给 Unknown Failure Policy ``` -不得机械透传第三方错误模型,也不得因为第三方返回了错误码,就强制在本系统中继续使用错误码。 +不得机械透传第三方错误模型,也不得因为第三方返回错误码,就强制在本系统继续使用错误码。 ## 5. 其他要求 @@ -67,4 +126,5 @@ exception 不得为了错误处理增加重复检查、兼容层、无意义 `try/catch`,也不得改变原函数的既有语义。 -**最终原则:已知结果使用返回码;未知失败使用可配置的 Unknown Failure Policy;第三方错误进入系统后重新按同一规则分类。** +**最终原则:每个函数独立定义自己的已知返回结果;Unknown Failure 使用可配置的 Failure +Policy;第三方结果进入系统后重新按同一规则分类。** diff --git a/Qt/plot/Explicit_Plot.cpp b/Qt/plot/Explicit_Plot.cpp index d268b37..8f3409f 100644 --- a/Qt/plot/Explicit_Plot.cpp +++ b/Qt/plot/Explicit_Plot.cpp @@ -8,7 +8,8 @@ Explicit_Plot::Explicit_Plot() : Abs_Plot(new Abs_Plot_Private(false)) {} Render_Ticket Explicit_Plot::replot() { - return {d->scene->render_frame(true) == Plot_Render_Result::none}; + const auto result = d->scene->render_frame(true); + return {result && *result == Plot_Render_Status::rendered}; } } // namespace renderive diff --git a/Qt/plot/Latency_Eager_Plot.cpp b/Qt/plot/Latency_Eager_Plot.cpp index f8c22f4..a28c422 100644 --- a/Qt/plot/Latency_Eager_Plot.cpp +++ b/Qt/plot/Latency_Eager_Plot.cpp @@ -23,9 +23,12 @@ double Latency_Eager_Plot::max_render_fps() const { return d->scene->max_render_fps(); } -void Latency_Eager_Plot::set_max_render_fps(double fps) { - d->scene->set_max_render_fps(fps); - update_render_interval(); +tl::expected +Latency_Eager_Plot::set_max_render_fps(double fps) { + auto result = d->scene->set_max_render_fps(fps); + if (result) + update_render_interval(); + return result; } Low_Latency_Diagnostics Latency_Eager_Plot::diagnostics() const { diff --git a/Qt/plot/Latency_Eager_Plot.h b/Qt/plot/Latency_Eager_Plot.h index dfe76a3..d33227b 100644 --- a/Qt/plot/Latency_Eager_Plot.h +++ b/Qt/plot/Latency_Eager_Plot.h @@ -12,7 +12,8 @@ public: void pause(); [[nodiscard]] bool running() const; [[nodiscard]] double max_render_fps() const; - void set_max_render_fps(double fps); + [[nodiscard]] tl::expected + set_max_render_fps(double fps); [[nodiscard]] Low_Latency_Diagnostics diagnostics() const; [[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const; }; diff --git a/Qt/plot/Plot.cpp b/Qt/plot/Plot.cpp index c626944..59a0e4c 100644 --- a/Qt/plot/Plot.cpp +++ b/Qt/plot/Plot.cpp @@ -45,9 +45,10 @@ void Abs_Plot_Private::attach_widget(Abs_Plot* plot) { QObject::connect(timer, &QTimer::timeout, plot, [this] { if (!scene->view_active()) return; - const auto error = scene->render_frame(); - if (error == Plot_Render_Result::external_failure || - error == Plot_Render_Result::scene_shutting_down) + const auto result = scene->render_frame(); + if (!result && + (result.error() == Plot_Render_Frame_Error::external_failure || + result.error() == Plot_Render_Frame_Error::scene_shutting_down)) timer->stop(); }); } diff --git a/cmake/RenderiveConfig.cmake.in b/cmake/RenderiveConfig.cmake.in index d233052..0d0aa3f 100644 --- a/cmake/RenderiveConfig.cmake.in +++ b/cmake/RenderiveConfig.cmake.in @@ -12,6 +12,7 @@ set(Renderive_Qt_FOUND @RENDERIVE_INSTALL_QT@) if (@RENDERIVE_INSTALL_2D@) find_dependency(blend2d CONFIG) + find_dependency(tl-expected CONFIG) endif () if (@RENDERIVE_INSTALL_3D@) diff --git a/render_2D/CMakeLists.txt b/render_2D/CMakeLists.txt index e754abc..32fd74e 100644 --- a/render_2D/CMakeLists.txt +++ b/render_2D/CMakeLists.txt @@ -1,4 +1,4 @@ -set(Renderive_render_2D_dependencies global::blend2d) +set(Renderive_render_2D_dependencies global::blend2d global::expected) if (RENDERIVE_BUILD_TESTS) set(Renderive_render_2D_test_targets) list(APPEND Renderive_render_2D_dependencies global::GTest) @@ -12,6 +12,7 @@ if (Renderive_render_2D_dependencies_missing) endif () rcl_load_dependency_environment(${Renderive_render_2D_dependencies}) find_package(blend2d CONFIG REQUIRED) +find_package(tl-expected CONFIG REQUIRED) if (RENDERIVE_BUILD_TESTS) find_package(GTest CONFIG REQUIRED) endif () @@ -29,7 +30,10 @@ target_include_directories(Renderive_render_2D PUBLIC "$" ) target_compile_features(Renderive_render_2D PUBLIC cxx_std_20) -target_link_libraries(Renderive_render_2D PUBLIC Renderive_Kernel blend2d::blend2d) +target_link_libraries(Renderive_render_2D PUBLIC + Renderive_Kernel + blend2d::blend2d + tl::expected) if (MSVC) target_compile_options(Renderive_render_2D PRIVATE /utf-8) endif () diff --git a/render_2D/render_2D/scene/Plot_Scene.cpp b/render_2D/render_2D/scene/Plot_Scene.cpp index 1cff1b6..6f093de 100644 --- a/render_2D/render_2D/scene/Plot_Scene.cpp +++ b/render_2D/render_2D/scene/Plot_Scene.cpp @@ -109,15 +109,15 @@ void Plot_Scene::dispatch_event(const Event& event) { impl_->apply([&event](auto& scene) { scene.dispatch_event(event); }); } -Plot_Render_Result Plot_Scene::prepare_frame() { +tl::expected Plot_Scene::prepare_frame() { return impl_->apply([](auto& scene) { return scene.prepare_frame(); }); } -Plot_Render_Result Plot_Scene::refresh_manual_frame() { +tl::expected Plot_Scene::refresh_manual_frame() { return impl_->apply([](auto& scene) { return scene.refresh_manual_frame(); }); } -Plot_Render_Result Plot_Scene::render_prepared_frame() { +tl::expected Plot_Scene::render_prepared_frame() { return impl_->apply([](auto& scene) { return scene.render_prepared_frame(); }); } @@ -125,7 +125,8 @@ bool Plot_Scene::discard_pending_frame() { return impl_->apply([](auto& scene) { return scene.discard_pending_frame(); }); } -Plot_Render_Result Plot_Scene::render_frame(bool force) { +tl::expected +Plot_Scene::render_frame(bool force) { return impl_->apply([force](auto& scene) { return scene.render_frame(force); }); } @@ -147,10 +148,12 @@ bool Plot_Scene::view_active() const noexcept { void Plot_Scene::request_redraw() noexcept { render_scene().notify_model_dirty(); } -Plot_Control_Error Plot_Scene::set_max_render_fps(double fps) { +tl::expected +Plot_Scene::set_max_render_fps(double fps) { return impl_->apply([fps](auto& scene) { return scene.set_max_render_fps(fps); }); } -Plot_Control_Error Plot_Scene::clear_max_render_fps() { +tl::expected +Plot_Scene::clear_max_render_fps() { return impl_->apply([](auto& scene) { return scene.clear_max_render_fps(); }); } @@ -158,10 +161,12 @@ double Plot_Scene::max_render_fps() const { return impl_->apply([](const auto& scene) { return scene.max_render_fps(); }); } -Plot_Control_Error Plot_Scene::set_consumer_feedback(Frame_Consumer_Feedback feedback) { +tl::expected +Plot_Scene::set_consumer_feedback(Frame_Consumer_Feedback feedback) { return impl_->apply([feedback](auto& scene) { return scene.set_consumer_feedback(feedback); }); } -Plot_Control_Error Plot_Scene::clear_consumer_feedback() { +tl::expected +Plot_Scene::clear_consumer_feedback() { return impl_->apply([](auto& scene) { return scene.clear_consumer_feedback(); }); } diff --git a/render_2D/render_2D/scene/Plot_Scene.h b/render_2D/render_2D/scene/Plot_Scene.h index 5a276de..d2b4b56 100644 --- a/render_2D/render_2D/scene/Plot_Scene.h +++ b/render_2D/render_2D/scene/Plot_Scene.h @@ -10,6 +10,7 @@ #include #include +#include #include class Scene_Base; @@ -21,17 +22,27 @@ struct Performance_Overlay; struct Plot_Scene_Options { Plot_Frame_Mode frame_mode{Plot_Frame_Mode::Low_Latency}; }; -enum class Plot_Control_Error : std::uint8_t { - none, - unsupported_frame_mode, - invalid_max_render_fps, - invalid_consumer_feedback +enum class Plot_Prepare_Frame_Error : std::uint8_t { + painter_unavailable }; -enum class Plot_Render_Result : std::uint8_t { - none, +enum class Plot_Refresh_Manual_Frame_Error : std::uint8_t { + unsupported_frame_mode, + no_pending_frame +}; +enum class Plot_Render_Prepared_Frame_Error : std::uint8_t { + renderer_unavailable, + cancelled, + deadline_exceeded, + external_failure, + scene_shutting_down +}; +enum class Plot_Render_Status : std::uint8_t { + rendered, view_inactive, empty_viewport, - model_unchanged, + model_unchanged +}; +enum class Plot_Render_Frame_Error : std::uint8_t { painter_unavailable, manual_refresh_not_supported, no_pending_frame, @@ -41,6 +52,20 @@ enum class Plot_Render_Result : std::uint8_t { external_failure, scene_shutting_down }; +enum class Plot_Set_Max_Render_Fps_Error : std::uint8_t { + unsupported_frame_mode, + invalid_fps +}; +enum class Plot_Clear_Max_Render_Fps_Error : std::uint8_t { + unsupported_frame_mode +}; +enum class Plot_Set_Consumer_Feedback_Error : std::uint8_t { + unsupported_frame_mode, + invalid_consumer_feedback +}; +enum class Plot_Clear_Consumer_Feedback_Error : std::uint8_t { + unsupported_frame_mode +}; struct Presentation_Sink { virtual ~Presentation_Sink() = default; @@ -70,11 +95,12 @@ public: [[nodiscard]] Size viewport_size() const; void dispatch_event(const Event& event); - [[nodiscard]] Plot_Render_Result prepare_frame(); - [[nodiscard]] Plot_Render_Result refresh_manual_frame(); - [[nodiscard]] Plot_Render_Result render_prepared_frame(); + [[nodiscard]] tl::expected prepare_frame(); + [[nodiscard]] tl::expected refresh_manual_frame(); + [[nodiscard]] tl::expected render_prepared_frame(); [[nodiscard]] bool discard_pending_frame(); - [[nodiscard]] Plot_Render_Result render_frame(bool force = false); + [[nodiscard]] tl::expected + render_frame(bool force = false); void with_frame(const std::function& consumer); void activate_view(); @@ -82,11 +108,14 @@ public: [[nodiscard]] bool view_active() const noexcept; void request_redraw() noexcept; - [[nodiscard]] Plot_Control_Error set_max_render_fps(double fps); - [[nodiscard]] Plot_Control_Error clear_max_render_fps(); + [[nodiscard]] tl::expected + set_max_render_fps(double fps); + [[nodiscard]] tl::expected clear_max_render_fps(); [[nodiscard]] double max_render_fps() const; - [[nodiscard]] Plot_Control_Error set_consumer_feedback(Frame_Consumer_Feedback feedback); - [[nodiscard]] Plot_Control_Error clear_consumer_feedback(); + [[nodiscard]] tl::expected + set_consumer_feedback(Frame_Consumer_Feedback feedback); + [[nodiscard]] tl::expected + clear_consumer_feedback(); [[nodiscard]] Plot_Frame_Mode frame_mode() const noexcept; [[nodiscard]] Plot_Frame_Status frame_status() const; diff --git a/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp b/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp index 9376868..ed517df 100644 --- a/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp +++ b/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp @@ -178,36 +178,43 @@ struct Plot_Scene_Model final : ::Scene2D_Context prepare_frame() { auto paint_frame = this->frame_control.acquire_painter(); if (!paint_frame) - return Plot_Render_Result::painter_unavailable; + return tl::unexpected(Plot_Prepare_Frame_Error::painter_unavailable); this->publish_frame_state(*paint_frame); - return Plot_Render_Result::none; + return {}; } - [[nodiscard]] Plot_Render_Result refresh_manual_frame() { + [[nodiscard]] tl::expected + refresh_manual_frame() { if constexpr (Mode == Plot_Frame_Mode::Manual) { - return this->frame_control.refresh() == Manual_Refresh_Result::none - ? Plot_Render_Result::none - : Plot_Render_Result::no_pending_frame; + if (this->frame_control.refresh() != Manual_Refresh_Result::none) + return tl::unexpected(Plot_Refresh_Manual_Frame_Error::no_pending_frame); + return {}; } - return Plot_Render_Result::manual_refresh_not_supported; + return tl::unexpected( + Plot_Refresh_Manual_Frame_Error::unsupported_frame_mode); } - [[nodiscard]] Plot_Render_Result render_prepared_frame() { + [[nodiscard]] tl::expected + render_prepared_frame() { auto render_frame = this->frame_control.acquire_renderer(); if (!render_frame) - return Plot_Render_Result::renderer_unavailable; + return tl::unexpected( + Plot_Render_Prepared_Frame_Error::renderer_unavailable); switch (Scene_Base::render(*render_frame)) { case Scene_Render_Result::none: - return Plot_Render_Result::none; + return {}; case Scene_Render_Result::cancelled: - return Plot_Render_Result::cancelled; + return tl::unexpected(Plot_Render_Prepared_Frame_Error::cancelled); case Scene_Render_Result::deadline_exceeded: - return Plot_Render_Result::deadline_exceeded; + return tl::unexpected( + Plot_Render_Prepared_Frame_Error::deadline_exceeded); case Scene_Render_Result::external_failure: - return Plot_Render_Result::external_failure; + return tl::unexpected( + Plot_Render_Prepared_Frame_Error::external_failure); case Scene_Render_Result::shutting_down: - return Plot_Render_Result::scene_shutting_down; + return tl::unexpected( + Plot_Render_Prepared_Frame_Error::scene_shutting_down); } ::renderive::error::unexpected("unknown scene render error"); } @@ -217,26 +224,55 @@ struct Plot_Scene_Model final : ::Scene2D_Contextframe_control.discard_pending_frame(); } - [[nodiscard]] Plot_Render_Result render_frame(bool force = false) { + [[nodiscard]] tl::expected + render_frame(bool force = false) { if (!view_active() && !force) - return Plot_Render_Result::view_inactive; + return Plot_Render_Status::view_inactive; if (viewport_size().empty()) - return Plot_Render_Result::empty_viewport; + return Plot_Render_Status::empty_viewport; if (!this->consume_model_dirty() && !force) - return Plot_Render_Result::model_unchanged; + return Plot_Render_Status::model_unchanged; const auto started = std::chrono::steady_clock::now(); Size viewport; try { - if (const auto error = prepare_frame(); error != Plot_Render_Result::none) - return error; + if (const auto result = prepare_frame(); !result) + return tl::unexpected(Plot_Render_Frame_Error::painter_unavailable); const auto frame_viewport = this->Scene_State_Strategy::render_state_value().viewport; viewport = {frame_viewport.width, frame_viewport.height}; if constexpr (Mode == Plot_Frame_Mode::Manual) { - if (const auto error = refresh_manual_frame(); error != Plot_Render_Result::none) - return error; + if (const auto result = refresh_manual_frame(); !result) { + switch (result.error()) { + case Plot_Refresh_Manual_Frame_Error::unsupported_frame_mode: + return tl::unexpected( + Plot_Render_Frame_Error::manual_refresh_not_supported); + case Plot_Refresh_Manual_Frame_Error::no_pending_frame: + return tl::unexpected( + Plot_Render_Frame_Error::no_pending_frame); + } + ::renderive::error::unexpected( + "unknown manual refresh error"); + } + } + if (const auto result = render_prepared_frame(); !result) { + switch (result.error()) { + case Plot_Render_Prepared_Frame_Error::renderer_unavailable: + return tl::unexpected( + Plot_Render_Frame_Error::renderer_unavailable); + case Plot_Render_Prepared_Frame_Error::cancelled: + return tl::unexpected(Plot_Render_Frame_Error::cancelled); + case Plot_Render_Prepared_Frame_Error::deadline_exceeded: + return tl::unexpected( + Plot_Render_Frame_Error::deadline_exceeded); + case Plot_Render_Prepared_Frame_Error::external_failure: + return tl::unexpected( + Plot_Render_Frame_Error::external_failure); + case Plot_Render_Prepared_Frame_Error::scene_shutting_down: + return tl::unexpected( + Plot_Render_Frame_Error::scene_shutting_down); + } + ::renderive::error::unexpected( + "unknown prepared frame render error"); } - if (const auto error = render_prepared_frame(); error != Plot_Render_Result::none) - return error; } catch (...) { this->notify_model_dirty(); @@ -256,7 +292,7 @@ struct Plot_Scene_Model final : ::Scene2D_Contextrenderable_count(), viewport); if (sink) sink->request_present(detail::full_rect(viewport)); - return Plot_Render_Result::none; + return Plot_Render_Status::rendered; } void with_frame(const std::function& consumer) { if (!consumer) @@ -275,43 +311,57 @@ struct Plot_Scene_Model final : ::Scene2D_Context + set_max_render_fps(double fps) { if constexpr (Mode != Plot_Frame_Mode::Low_Latency) { - return Plot_Control_Error::unsupported_frame_mode; - } else { + return tl::unexpected( + Plot_Set_Max_Render_Fps_Error::unsupported_frame_mode); + } + else { using Error = typename Frame_Control::Control_Error; const auto error = this->frame_control.set_frequency_hz(fps); if (error != Error::none) - return Plot_Control_Error::invalid_max_render_fps; + return tl::unexpected(Plot_Set_Max_Render_Fps_Error::invalid_fps); this->notify_model_dirty(); - return Plot_Control_Error::none; + return {}; } } - [[nodiscard]] Plot_Control_Error clear_max_render_fps() { + [[nodiscard]] tl::expected + clear_max_render_fps() { if constexpr (Mode != Plot_Frame_Mode::Low_Latency) { - return Plot_Control_Error::unsupported_frame_mode; - } else { + return tl::unexpected( + Plot_Clear_Max_Render_Fps_Error::unsupported_frame_mode); + } + else { this->frame_control.clear_frequency_limit(); this->notify_model_dirty(); - return Plot_Control_Error::none; + return {}; } } - [[nodiscard]] Plot_Control_Error set_consumer_feedback(Frame_Consumer_Feedback feedback) { + [[nodiscard]] tl::expected + set_consumer_feedback(Frame_Consumer_Feedback feedback) { if constexpr (Mode != Plot_Frame_Mode::Low_Latency) { - return Plot_Control_Error::unsupported_frame_mode; - } else { + return tl::unexpected( + Plot_Set_Consumer_Feedback_Error::unsupported_frame_mode); + } + else { using Error = typename Frame_Control::Control_Error; const auto error = this->frame_control.set_consumer_feedback(feedback); - return error == Error::none ? Plot_Control_Error::none - : Plot_Control_Error::invalid_consumer_feedback; + if (error != Error::none) + return tl::unexpected( + Plot_Set_Consumer_Feedback_Error::invalid_consumer_feedback); + return {}; } } - [[nodiscard]] Plot_Control_Error clear_consumer_feedback() { + [[nodiscard]] tl::expected + clear_consumer_feedback() { if constexpr (Mode != Plot_Frame_Mode::Low_Latency) { - return Plot_Control_Error::unsupported_frame_mode; - } else { + return tl::unexpected( + Plot_Clear_Consumer_Feedback_Error::unsupported_frame_mode); + } + else { this->frame_control.clear_consumer_feedback(); - return Plot_Control_Error::none; + return {}; } } [[nodiscard]] double max_render_fps() const { @@ -359,7 +409,7 @@ struct Plot_Scene_Model final : ::Scene2D_Contextframe_control.pending_frame_count(); snapshot.limit_state = detail::limit_state_name(static_cast(state.limit_state)); diff --git a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp index 62cf944..f65c515 100644 --- a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp +++ b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp @@ -9,7 +9,7 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualLifecycleSeparatesPrepareRefreshAndRe Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Manual}); plot.init(); plot.set_viewport_size({96, 54}); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.mode, Plot_Frame_Mode::Manual); EXPECT_EQ(snapshot.last_event, "prepared"); @@ -18,14 +18,14 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualLifecycleSeparatesPrepareRefreshAndRe EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 1u); EXPECT_EQ(snapshot.latest_sequence, 1u); - ASSERT_EQ(plot.refresh_manual_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.refresh_manual_frame()); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "refresh_succeeded"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.produced_frame_count, 1u); @@ -38,8 +38,8 @@ TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKern Plot_Scene plot; plot.init(); plot.set_viewport_size({96, 54}); - EXPECT_EQ(plot.set_max_render_fps(1'000'000'000.0), Plot_Control_Error::none); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + EXPECT_TRUE(plot.set_max_render_fps(1'000'000'000.0)); + ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "published"); EXPECT_EQ(snapshot.limit_state, "frequency_limited"); @@ -48,7 +48,7 @@ TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKern EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 1u); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "lifecycle_completed"); EXPECT_DOUBLE_EQ(snapshot.frequency_hz, 1'000'000'000.0); @@ -71,10 +71,10 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2 Plot_Scene plot; plot.init(); plot.set_viewport_size({96, 54}); - EXPECT_EQ(plot.set_max_render_fps(100.0), Plot_Control_Error::none); - EXPECT_EQ(plot.set_consumer_feedback({40'000'000}), Plot_Control_Error::none); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + EXPECT_TRUE(plot.set_max_render_fps(100.0)); + EXPECT_TRUE(plot.set_consumer_feedback({40'000'000})); + ASSERT_TRUE(plot.prepare_frame()); + ASSERT_TRUE(plot.render_prepared_frame()); const auto snapshot = plot.frame_status(); EXPECT_TRUE(snapshot.frequency_limit_enabled); EXPECT_TRUE(snapshot.consumer_feedback_enabled); @@ -92,12 +92,12 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyLimitsCanBeClearedIndependently) Plot_Scene plot; plot.init(); plot.set_viewport_size({96, 54}); - EXPECT_EQ(plot.set_max_render_fps(100.0), Plot_Control_Error::none); - EXPECT_EQ(plot.set_consumer_feedback({40'000'000}), Plot_Control_Error::none); - plot.clear_consumer_feedback(); - plot.clear_max_render_fps(); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + EXPECT_TRUE(plot.set_max_render_fps(100.0)); + EXPECT_TRUE(plot.set_consumer_feedback({40'000'000})); + ASSERT_TRUE(plot.clear_consumer_feedback()); + ASSERT_TRUE(plot.clear_max_render_fps()); + ASSERT_TRUE(plot.prepare_frame()); + ASSERT_TRUE(plot.render_prepared_frame()); const auto snapshot = plot.frame_status(); EXPECT_FALSE(snapshot.frequency_limit_enabled); EXPECT_FALSE(snapshot.consumer_feedback_enabled); @@ -110,15 +110,15 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyReplacementRetiresOldFrameWithout Plot_Scene plot; plot.init(); plot.set_viewport_size({64, 40}); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); + ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "published"); EXPECT_EQ(snapshot.produced_frame_count, 2u); EXPECT_EQ(snapshot.dropped_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 1u); EXPECT_EQ(snapshot.latest_sequence, 2u); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "lifecycle_completed"); EXPECT_EQ(snapshot.produced_frame_count, 2u); @@ -131,7 +131,7 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyDiscardAndEmptyRenderRemainObserv Plot_Scene plot; plot.init(); plot.set_viewport_size({64, 40}); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.discard_pending_frame()); auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "manually_discarded"); @@ -140,7 +140,10 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyDiscardAndEmptyRenderRemainObserv EXPECT_EQ(snapshot.dropped_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); - EXPECT_NE(plot.render_prepared_frame(), Plot_Render_Result::none); + const auto empty_render = plot.render_prepared_frame(); + ASSERT_FALSE(empty_render); + EXPECT_EQ(empty_render.error(), + Plot_Render_Prepared_Frame_Error::renderer_unavailable); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "swap_failed"); EXPECT_EQ(snapshot.failed_operation_count, 1u); @@ -151,7 +154,7 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualDiscardAndFailedRefreshUpdateObserver Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Manual}); plot.init(); plot.set_viewport_size({64, 40}); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.discard_pending_frame()); auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "manually_discarded"); @@ -160,7 +163,10 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualDiscardAndFailedRefreshUpdateObserver EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); const auto last_paint_duration_ns = snapshot.paint_duration_ns; - EXPECT_NE(plot.refresh_manual_frame(), Plot_Render_Result::none); + const auto failed_refresh = plot.refresh_manual_frame(); + ASSERT_FALSE(failed_refresh); + EXPECT_EQ(failed_refresh.error(), + Plot_Refresh_Manual_Frame_Error::no_pending_frame); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "refresh_failed"); EXPECT_EQ(snapshot.failed_operation_count, 1u); @@ -172,16 +178,16 @@ TEST(Renderive_Core2_Frame_Pipeline, PlaybackPreservesQueueDepthAndEmptyAcquireF Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Playback}); plot.init(); plot.set_viewport_size({64, 40}); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); + ASSERT_TRUE(plot.prepare_frame()); + ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.produced_frame_count, 3u); EXPECT_EQ(snapshot.pending_frame_count, 3u); EXPECT_EQ(snapshot.latest_sequence, 3u); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.render_prepared_frame()); + ASSERT_TRUE(plot.render_prepared_frame()); + ASSERT_TRUE(plot.render_prepared_frame()); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.consumed_frame_count, 3u); @@ -190,7 +196,7 @@ TEST(Renderive_Core2_Frame_Pipeline, PlaybackPreservesQueueDepthAndEmptyAcquireF const auto last_paint_duration_ns = snapshot.paint_duration_ns; const auto last_render_duration_ns = snapshot.render_duration_ns; const auto last_queue_wait_ns = snapshot.queue_wait_ns; - EXPECT_NE(plot.render_prepared_frame(), Plot_Render_Result::none); + EXPECT_FALSE(plot.render_prepared_frame()); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "queue_empty"); EXPECT_EQ(snapshot.failed_operation_count, 1u); @@ -204,13 +210,13 @@ TEST(Renderive_Core2_Frame_Pipeline, PlaybackFullCyclePreservesPreviouslyQueuedF Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Playback}); plot.init(); plot.set_viewport_size({96, 54}); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); + ASSERT_TRUE(plot.prepare_frame()); + ASSERT_TRUE(plot.prepare_frame()); auto snapshot = plot.frame_status(); ASSERT_EQ(snapshot.pending_frame_count, 3u); ASSERT_EQ(snapshot.produced_frame_count, 3u); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); snapshot = plot.frame_status(); EXPECT_EQ(snapshot.mode, Plot_Frame_Mode::Playback); EXPECT_EQ(snapshot.last_event, "rendered"); @@ -228,19 +234,19 @@ TEST(Renderive_Core2_Frame_Pipeline, EveryModeHonorsActiveDirtyAndForceRenderGat scene.init(); scene.set_viewport_size({80, 45}); EXPECT_FALSE(scene.view_active()); - EXPECT_NE(scene.render_frame(false), Plot_Render_Result::none); + EXPECT_NE(scene.render_frame(false).value(), Plot_Render_Status::rendered); EXPECT_EQ(scene.frame_status().observation_count, 0u); scene.activate_view(); - ASSERT_EQ(scene.render_frame(false), Plot_Render_Result::none); + ASSERT_EQ(scene.render_frame(false).value(), Plot_Render_Status::rendered); const auto completed = scene.frame_status(); EXPECT_EQ(completed.mode, mode); EXPECT_EQ(completed.consumed_frame_count, 1u); - EXPECT_NE(scene.render_frame(false), Plot_Render_Result::none); + EXPECT_NE(scene.render_frame(false).value(), Plot_Render_Status::rendered); EXPECT_EQ(scene.frame_status().consumed_frame_count, 1u); scene.deactivate_view(); scene.set_background_color(Color{8, 16, 24, 255}); - EXPECT_NE(scene.render_frame(false), Plot_Render_Result::none); - ASSERT_EQ(scene.render_frame(true), Plot_Render_Result::none); + EXPECT_NE(scene.render_frame(false).value(), Plot_Render_Status::rendered); + ASSERT_EQ(scene.render_frame(true).value(), Plot_Render_Status::rendered); EXPECT_EQ(scene.frame_status().consumed_frame_count, 2u); }; verify(Plot_Frame_Mode::Manual); @@ -249,19 +255,22 @@ TEST(Renderive_Core2_Frame_Pipeline, EveryModeHonorsActiveDirtyAndForceRenderGat } TEST(Renderive_Core2_Frame_Pipeline, FrequencyConfigurationRejectsInvalidValuesAndWrongModes) { Plot_Scene low_latency; - EXPECT_EQ(low_latency.set_max_render_fps(0.0), Plot_Control_Error::invalid_max_render_fps); - EXPECT_EQ(low_latency.set_max_render_fps(-1.0), Plot_Control_Error::invalid_max_render_fps); - EXPECT_EQ(low_latency.set_max_render_fps(std::numeric_limits::quiet_NaN()), - Plot_Control_Error::invalid_max_render_fps); - EXPECT_EQ(low_latency.set_max_render_fps(std::numeric_limits::infinity()), - Plot_Control_Error::invalid_max_render_fps); - EXPECT_EQ(low_latency.set_max_render_fps(1'000.0), Plot_Control_Error::none); + EXPECT_EQ(low_latency.set_max_render_fps(0.0).error(), Plot_Set_Max_Render_Fps_Error::invalid_fps); + EXPECT_EQ(low_latency.set_max_render_fps(-1.0).error(), Plot_Set_Max_Render_Fps_Error::invalid_fps); + EXPECT_EQ(low_latency.set_max_render_fps(std::numeric_limits::quiet_NaN()).error(), + Plot_Set_Max_Render_Fps_Error::invalid_fps); + EXPECT_EQ(low_latency.set_max_render_fps(std::numeric_limits::infinity()).error(), + Plot_Set_Max_Render_Fps_Error::invalid_fps); + EXPECT_TRUE(low_latency.set_max_render_fps(1'000.0)); EXPECT_DOUBLE_EQ(low_latency.max_render_fps(), 1'000.0); Plot_Scene manual({.frame_mode = Plot_Frame_Mode::Manual}); Plot_Scene playback({.frame_mode = Plot_Frame_Mode::Playback}); - EXPECT_EQ(manual.set_max_render_fps(60.0), Plot_Control_Error::unsupported_frame_mode); - EXPECT_EQ(playback.set_max_render_fps(60.0), Plot_Control_Error::unsupported_frame_mode); - EXPECT_NE(playback.refresh_manual_frame(), Plot_Render_Result::none); + EXPECT_EQ(manual.set_max_render_fps(60.0).error(), Plot_Set_Max_Render_Fps_Error::unsupported_frame_mode); + EXPECT_EQ(playback.set_max_render_fps(60.0).error(), Plot_Set_Max_Render_Fps_Error::unsupported_frame_mode); + const auto unsupported_refresh = playback.refresh_manual_frame(); + ASSERT_FALSE(unsupported_refresh); + EXPECT_EQ(unsupported_refresh.error(), + Plot_Refresh_Manual_Frame_Error::unsupported_frame_mode); EXPECT_FALSE(playback.discard_pending_frame()); } } // namespace diff --git a/render_2D/tests/render_2D_Integration_Tests.cpp b/render_2D/tests/render_2D_Integration_Tests.cpp index 426adda..8bedb07 100644 --- a/render_2D/tests/render_2D_Integration_Tests.cpp +++ b/render_2D/tests/render_2D_Integration_Tests.cpp @@ -61,8 +61,8 @@ TEST(Renderive_Core2, RootIdentityAndRefreshDiagnosticsComeFromKernelState) { EXPECT_EQ(plot.root_renderable(), root); plot.set_viewport_size({32, 24}); plot.activate_view(); - EXPECT_EQ(plot.set_max_render_fps(144.0), Plot_Control_Error::none); - ASSERT_EQ(plot.render_frame(), Plot_Render_Result::none); + EXPECT_TRUE(plot.set_max_render_fps(144.0)); + ASSERT_EQ(plot.render_frame().value(), Plot_Render_Status::rendered); const auto diagnostics = plot.diagnostics(); EXPECT_DOUBLE_EQ(diagnostics.refresh.frequency_hz, 144.0); EXPECT_EQ(diagnostics.refresh.frame_count, 1u); @@ -194,7 +194,7 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { for (std::size_t index = 0; index < samples.size(); ++index) samples[index] = -100.0 + static_cast(index % 24) * 3.0; spectrum->update_samples(samples); - ASSERT_EQ(plot.render_frame(), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame().value(), Plot_Render_Status::rendered); bool saw_frame = false; bool saw_drawn_pixel = false; plot.with_frame([&](Image_View view) { @@ -214,10 +214,10 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { EXPECT_TRUE(saw_frame); EXPECT_TRUE(saw_drawn_pixel); EXPECT_EQ(plot.diagnostics().refresh.frame_count, 1u); - EXPECT_NE(plot.render_frame(), Plot_Render_Result::none); + EXPECT_NE(plot.render_frame().value(), Plot_Render_Status::rendered); samples.front() += 1.0; spectrum->update_samples(samples); - EXPECT_EQ(plot.render_frame(true), Plot_Render_Result::none); + EXPECT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); frequency_axis->set<&Axis_Properties::wheel>(true); const Range before_zoom = frequency_axis->get<&Axis_Properties::coordinates>(); Wheel_Event wheel; @@ -226,7 +226,7 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { plot.dispatch_event(wheel); EXPECT_TRUE(wheel.is_accepted()); EXPECT_LT(frequency_axis->get<&Axis_Properties::coordinates>().size(), before_zoom.size()); - EXPECT_EQ(plot.render_frame(), Plot_Render_Result::none); + EXPECT_EQ(plot.render_frame().value(), Plot_Render_Status::rendered); apply_runtime_edit(plot, [root, frequency_axis, power_axis, spectrum](auto& editor) { editor.set_display_parent(frequency_axis, root); editor.set_display_parent(power_axis, root); @@ -234,8 +234,8 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { editor.clear_dependency_parent(spectrum); editor.detach(spectrum); }); - EXPECT_EQ(plot.render_frame(true), Plot_Render_Result::none); - EXPECT_NE(plot.render_frame(), Plot_Render_Result::none); + EXPECT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); + EXPECT_NE(plot.render_frame().value(), Plot_Render_Status::rendered); } TEST(Renderive_Core2, StandaloneRenderableBuilderJoinsSceneOnlyInsideRuntimeEditor) { Plot_Scene plot; @@ -296,14 +296,14 @@ TEST(Renderive_Core2, PerformanceOverlayConsumesKernelFrameDiagnostics) { plot.init(); plot.set_viewport_size({160, 90}); plot.activate_view(); - EXPECT_EQ(plot.set_max_render_fps(120.0), Plot_Control_Error::none); + EXPECT_TRUE(plot.set_max_render_fps(120.0)); Performance_Overlay_Options options; options.log.enabled = false; const auto overlay = attach_performance_overlay(plot, options); ASSERT_TRUE(overlay); set_performance_plot_name(plot, "integration"); set_performance_overlay_enabled(plot, true); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); const auto snapshot = overlay->display_snapshot(); ASSERT_TRUE(snapshot); EXPECT_EQ(snapshot->plot_name, "integration"); @@ -317,12 +317,12 @@ TEST(Renderive_Core2, AllFrameControlModesUseKernelStrategiesAndExposeObservers) manual.init(); manual.set_viewport_size({64, 40}); EXPECT_EQ(manual.frame_mode(), Plot_Frame_Mode::Manual); - ASSERT_EQ(manual.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(manual.prepare_frame()); auto manual_observer = manual.frame_status(); EXPECT_EQ(manual_observer.last_event, "prepared"); EXPECT_EQ(manual_observer.pending_frame_count, 1u); - ASSERT_EQ(manual.refresh_manual_frame(), Plot_Render_Result::none); - ASSERT_EQ(manual.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(manual.refresh_manual_frame()); + ASSERT_TRUE(manual.render_prepared_frame()); manual_observer = manual.frame_status(); EXPECT_EQ(manual_observer.last_event, "rendered"); EXPECT_EQ(manual_observer.consumed_frame_count, 1u); @@ -330,7 +330,7 @@ TEST(Renderive_Core2, AllFrameControlModesUseKernelStrategiesAndExposeObservers) low_latency.init(); low_latency.set_viewport_size({64, 40}); low_latency.activate_view(); - ASSERT_EQ(low_latency.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(low_latency.render_frame(true).value(), Plot_Render_Status::rendered); const auto low_observer = low_latency.frame_status(); EXPECT_EQ(low_latency.frame_mode(), Plot_Frame_Mode::Low_Latency); EXPECT_EQ(low_observer.last_event, "lifecycle_completed"); @@ -338,14 +338,14 @@ TEST(Renderive_Core2, AllFrameControlModesUseKernelStrategiesAndExposeObservers) Plot_Scene playback({.frame_mode = Plot_Frame_Mode::Playback}); playback.init(); playback.set_viewport_size({64, 40}); - ASSERT_EQ(playback.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(playback.prepare_frame(), Plot_Render_Result::none); - ASSERT_EQ(playback.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(playback.prepare_frame()); + ASSERT_TRUE(playback.prepare_frame()); + ASSERT_TRUE(playback.prepare_frame()); auto playback_observer = playback.frame_status(); EXPECT_EQ(playback.frame_mode(), Plot_Frame_Mode::Playback); EXPECT_EQ(playback_observer.last_event, "enqueued"); EXPECT_EQ(playback_observer.pending_frame_count, 3u); - ASSERT_EQ(playback.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(playback.render_prepared_frame()); playback_observer = playback.frame_status(); EXPECT_EQ(playback_observer.last_event, "rendered"); EXPECT_EQ(playback_observer.pending_frame_count, 2u); @@ -520,7 +520,7 @@ TEST(Renderive_Core2, RetainedSpectrumApisRoundTripAndMarkersRemainObservable) { EXPECT_EQ(spectrum->selectable_line_marker_count(), 0); spectrum->clear_custom_markers(); EXPECT_EQ(spectrum->selected_marker_index(), -1); - EXPECT_EQ(plot.render_frame(true), Plot_Render_Result::none); + EXPECT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); } TEST(Renderive_Core2, RetainedHeatmapSweepAndTraceApisPreserveDataShapes) { Plot_Scene plot; @@ -634,7 +634,7 @@ TEST(Renderive_Core2, RetainedHeatmapSweepAndTraceApisPreserveDataShapes) { trace->append_sample(Time_Of_Day{4000}, -70.0); EXPECT_EQ(trace->sample_count(), 2U); EXPECT_EQ(trace->rendered_point_count(), 2U); - EXPECT_EQ(plot.render_frame(true), Plot_Render_Result::none); + EXPECT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); } TEST(Renderive_Core2, RetainedSelectionAndConstellationApisDriveInteractionAndLayout) { Plot_Scene plot; @@ -713,7 +713,7 @@ TEST(Renderive_Core2, RetainedSelectionAndConstellationApisDriveInteractionAndLa constellation->fit_square_to_axes(); EXPECT_EQ(horizontal->get<&Axis_Properties::coordinates>(), (Range{-3.0, 3.0})); EXPECT_EQ(vertical->get<&Axis_Properties::coordinates>(), (Range{3.0, -3.0})); - EXPECT_EQ(plot.render_frame(true), Plot_Render_Result::none); + EXPECT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); } TEST(Renderive_Core2, PlottablePropertiesPublishOnlyAtFrameBoundary) { Plot_Scene plot; @@ -738,12 +738,12 @@ TEST(Renderive_Core2, PlottablePropertiesPublishOnlyAtFrameBoundary) { EXPECT_EQ(cached.event, Renderable_Observer_Event::Cache_Updated); EXPECT_EQ(cached.cache_update_count, 1U); EXPECT_EQ(cached.publish_count, 0U); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); const auto published = spectrum->observation(); EXPECT_EQ(published.event, Renderable_Observer_Event::Published); EXPECT_EQ(published.cache_update_count, 1U); EXPECT_EQ(published.publish_count, 1U); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.render_prepared_frame()); } TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) { Plot_Scene plot; @@ -777,7 +777,7 @@ TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) { EXPECT_EQ(spectrum_update.cache_update_count, 1U); EXPECT_EQ(power->observation().event, Renderable_Observer_Event::None); - ASSERT_EQ(plot.prepare_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.prepare_frame()); const auto frequency_publish = frequency->observation(); const auto power_publish = power->observation(); const auto spectrum_publish = spectrum->observation(); @@ -791,7 +791,7 @@ TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) { EXPECT_EQ(power_publish.cache_update_count, 0U); EXPECT_EQ(spectrum_publish.cache_update_count, 1U); - ASSERT_EQ(plot.render_prepared_frame(), Plot_Render_Result::none); + ASSERT_TRUE(plot.render_prepared_frame()); EXPECT_EQ(frequency->observation().event, Renderable_Observer_Event::Published); EXPECT_EQ(power->observation().event, Renderable_Observer_Event::Published); EXPECT_EQ(spectrum->observation().event, Renderable_Observer_Event::Published); @@ -922,7 +922,7 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { EXPECT_EQ(spectrum->get<&Spectrum::Properties::partition_count>(), 4); EXPECT_EQ(waterfall->get<&Waterfall::Properties::partition_count>(), 4); EXPECT_EQ(afterglow->get<&Afterglow::Properties::partition_count>(), 4); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); const auto partitioned_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(partitioned_plan); EXPECT_EQ(planned_node_count(*partitioned_plan, *spectrum), 6u); @@ -941,7 +941,7 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { waterfall->set<&Waterfall::Properties::partition_count>(1); afterglow->set<&Afterglow::Properties::partition_count>(1); plot.render_scene().publish_frame_state(); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); const auto single_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(single_plan); EXPECT_EQ(planned_node_count(*single_plan, *spectrum), 3u); @@ -952,7 +952,7 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { waterfall->set<&Waterfall::Properties::partition_mode>(Render_Partition_Mode::Automatic); afterglow->set<&Afterglow::Properties::partition_mode>(Render_Partition_Mode::Automatic); plot.render_scene().publish_frame_state(); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); const auto automatic_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(automatic_plan); EXPECT_EQ(planned_node_count(*automatic_plan, *spectrum), 3u); @@ -1028,7 +1028,7 @@ TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSe } if (frame % 4 == 0) waterfall->append_row(static_cast(tick++), samples); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); } ASSERT_EQ(scene.render_scene().wait_for_render(), Scene_Render_Result::none); const auto session = scene.render_scene().capture_session(session_id); @@ -1069,8 +1069,8 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { }); const std::array samples{-80.0, -70.0, -60.0, -50.0}; spectrum->update_samples(samples); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); ASSERT_EQ(plot.render_scene().wait_for_render(), Scene_Render_Result::none); const auto cached_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(cached_plan); @@ -1087,7 +1087,7 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { spectrum->set<&Spectrum::Properties::current_pen>( Pen{Color{12, 34, 56, 255}, 2.0}); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); ASSERT_EQ(plot.render_scene().wait_for_render(), Scene_Render_Result::none); const auto plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(plan); @@ -1102,7 +1102,7 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { EXPECT_TRUE(has_kind(Render_Node_Kind::paint)); frequency->set<&Axis_Base_Properties::color>(Color{90, 100, 110, 255}); - ASSERT_EQ(plot.render_frame(true), Plot_Render_Result::none); + ASSERT_EQ(plot.render_frame(true).value(), Plot_Render_Status::rendered); ASSERT_EQ(plot.render_scene().wait_for_render(), Scene_Render_Result::none); const auto axis_style_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(axis_style_plan); diff --git a/web_server/app/Gallery_Session_Control_Adminive.h b/web_server/app/Gallery_Session_Control_Adminive.h index b7fa5d2..1cf2c9c 100644 --- a/web_server/app/Gallery_Session_Control_Adminive.h +++ b/web_server/app/Gallery_Session_Control_Adminive.h @@ -91,10 +91,11 @@ struct Type_Descriptor> { [dirty](T& value, bool enabled) { if (enabled) { if (value.scene_.max_render_fps() <= 0.0 && - value.scene_.set_max_render_fps(30.0) != renderive::Plot_Control_Error::none) + !value.scene_.set_max_render_fps(30.0)) return; } else { - value.scene_.clear_max_render_fps(); + if (!value.scene_.clear_max_render_fps()) + return; } dirty(value); }), @@ -106,7 +107,7 @@ struct Type_Descriptor> { }, [dirty](T& value, double fps) { if (value.scene_.max_render_fps() > 0.0 && - value.scene_.set_max_render_fps(fps) != renderive::Plot_Control_Error::none) + !value.scene_.set_max_render_fps(fps)) return; dirty(value); }), diff --git a/web_server/app/Web_Plot_Session.cpp b/web_server/app/Web_Plot_Session.cpp index a0cf7b8..6451a47 100644 --- a/web_server/app/Web_Plot_Session.cpp +++ b/web_server/app/Web_Plot_Session.cpp @@ -1,11 +1,13 @@ #include "Web_Plot_Session.h" #include "Pixel_Frame.h" #include "render_2D/export.h" +#include #include #include #include #include #include +#include #include #include #include @@ -64,7 +66,9 @@ struct Web_Plot_Session::Impl { Impl() { plot.init(); plot.set_background_color({3, 7, 18, 255}); - static_cast(plot.set_max_render_fps(30.0)); + if (!plot.set_max_render_fps(30.0)) + ::renderive::error::unexpected( + "validated web plot frame rate was rejected"); plot.set_viewport_size({960, 600}); const auto root = plot.root_renderable(); constexpr Color axis_color{93, 116, 151, 255}; @@ -160,7 +164,7 @@ struct Web_Plot_Session::Impl { plot.activate_view(); update_model(); const auto initial_render = plot.render_frame(true); - if (initial_render != Plot_Render_Result::none) + if (!initial_render || *initial_render != Plot_Render_Status::rendered) plot.request_redraw(); } ~Impl() { @@ -268,7 +272,8 @@ struct Web_Plot_Session::Impl { if (!plot.view_active()) return std::nullopt; update_model(); - if (plot.render_frame(true) != Plot_Render_Result::none) + const auto render = plot.render_frame(true); + if (!render || *render != Plot_Render_Status::rendered) return std::nullopt; std::string pixels; const Color background = plot.background_color(); diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index 820da3b..1536448 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -29,23 +29,36 @@ #include namespace renderive::web { namespace { -Gallery_Render_Result gallery_render_error(Plot_Render_Result error) { - switch (error) { - case Plot_Render_Result::none: - return Gallery_Render_Result::none; - case Plot_Render_Result::view_inactive: - return Gallery_Render_Result::inactive; - case Plot_Render_Result::cancelled: +Gallery_Render_Result gallery_render_result( + const tl::expected& result) { + if (result) { + switch (*result) { + case Plot_Render_Status::rendered: + return Gallery_Render_Result::none; + case Plot_Render_Status::view_inactive: + return Gallery_Render_Result::inactive; + case Plot_Render_Status::empty_viewport: + case Plot_Render_Status::model_unchanged: + return Gallery_Render_Result::frame_unavailable; + } + ::renderive::error::unexpected("unknown plot render status"); + } + switch (result.error()) { + case Plot_Render_Frame_Error::cancelled: return Gallery_Render_Result::cancelled; - case Plot_Render_Result::deadline_exceeded: + case Plot_Render_Frame_Error::deadline_exceeded: return Gallery_Render_Result::deadline_exceeded; - case Plot_Render_Result::external_failure: + case Plot_Render_Frame_Error::external_failure: return Gallery_Render_Result::backend_failure; - case Plot_Render_Result::scene_shutting_down: + case Plot_Render_Frame_Error::scene_shutting_down: return Gallery_Render_Result::shutting_down; - default: + case Plot_Render_Frame_Error::painter_unavailable: + case Plot_Render_Frame_Error::manual_refresh_not_supported: + case Plot_Render_Frame_Error::no_pending_frame: + case Plot_Render_Frame_Error::renderer_unavailable: return Gallery_Render_Result::frame_unavailable; } + ::renderive::error::unexpected("unknown plot render error"); } template void update_axis_state(const renderive_Owner& axis, Update&& update) { @@ -209,11 +222,14 @@ public: plot_, *primary_, feedback_policy_); register_controls(); set_performance_plot_name(plot_, case_id_ + "/" + gallery_enum_id(frame_mode_)); - if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) - static_cast(plot_.set_max_render_fps(30.0)); + if (frame_mode_ == Gallery_Frame_Mode::Low_Latency && + !plot_.set_max_render_fps(30.0)) + ::renderive::error::unexpected( + "validated gallery frame rate was rejected"); plot_.activate_view(); update_model(); - if (plot_.render_frame(true) != Plot_Render_Result::none) + const auto initial_render = plot_.render_frame(true); + if (!initial_render || *initial_render != Plot_Render_Status::rendered) plot_.request_redraw(); } ~Gallery_Scene() { @@ -354,12 +370,12 @@ public: return Gallery_Render_Result::inactive; update_model(); const auto started = std::chrono::steady_clock::now(); - const auto error = plot_.render_frame(true); - const bool rendered = error == Plot_Render_Result::none; + const auto result = plot_.render_frame(true); + const bool rendered = result && *result == Plot_Render_Status::rendered; record_performance(started, rendered); if (rendered) rendered_since_last_pixel_ = true; - return gallery_render_error(error); + return gallery_render_result(result); } [[nodiscard]] std::optional encode_latest_pixels() { if (!plot_.view_active()) @@ -420,7 +436,7 @@ public: } if (request.id == "mode_prepare" || request.id == "mode_enqueue") { update_model(); - const bool prepared = plot_.prepare_frame() == Plot_Render_Result::none; + const bool prepared = plot_.prepare_frame().has_value(); last_action_result_ = prepared ? "frame prepared" : "prepare rejected"; return prepared ? "Kernel 帧已准备/入队" : "Kernel 拒绝准备帧"; } @@ -432,19 +448,19 @@ public: int prepared{}; for (int index = 0; index < count; ++index) { update_model(); - prepared += plot_.prepare_frame() == Plot_Render_Result::none ? 1 : 0; + prepared += plot_.prepare_frame().has_value() ? 1 : 0; } last_action_result_ = "enqueued=" + std::to_string(prepared); return "回放帧已批量压入 Flow 队列"; } if (request.id == "mode_refresh") { - const bool refreshed = plot_.refresh_manual_frame() == Plot_Render_Result::none; + const bool refreshed = plot_.refresh_manual_frame().has_value(); last_action_result_ = refreshed ? "manual refresh succeeded" : "manual refresh failed"; return refreshed ? "Manual 待处理帧已提交刷新" : "当前没有可刷新的 Manual 帧"; } if (request.id == "mode_render" || request.id == "mode_dequeue") { const auto started = std::chrono::steady_clock::now(); - const bool rendered = plot_.render_prepared_frame() == Plot_Render_Result::none; + const bool rendered = plot_.render_prepared_frame().has_value(); record_performance(started, rendered); rendered_since_last_pixel_ = rendered; last_action_result_ = rendered ? "prepared frame rendered" : "no prepared frame"; @@ -458,7 +474,8 @@ public: if (request.id == "mode_cycle") { update_model(); const auto started = std::chrono::steady_clock::now(); - const bool rendered = plot_.render_frame(true) == Plot_Render_Result::none; + const auto result = plot_.render_frame(true); + const bool rendered = result && *result == Plot_Render_Status::rendered; record_performance(started, rendered); rendered_since_last_pixel_ = rendered; last_action_result_ = rendered ? "full frame cycle rendered" : "frame cycle skipped"; @@ -916,7 +933,7 @@ private: consumer_manual_interval_ns_ = frequency_to_ns(feedback_policy_.manual_fps); consumer_feedback_source_ = "none"; if (!feedback_policy_.enabled) { - if (plot_.clear_consumer_feedback() != Plot_Control_Error::none) + if (!plot_.clear_consumer_feedback()) ::renderive::error::unexpected("low-latency feedback clearing failed"); consumer_feedback_source_ = "disabled"; return; @@ -944,11 +961,11 @@ private: select_source(feedback_policy_.manual, consumer_manual_interval_ns_, "manual"); if (interval_ns == 0) { - if (plot_.clear_consumer_feedback() != Plot_Control_Error::none) + if (!plot_.clear_consumer_feedback()) ::renderive::error::unexpected("low-latency feedback clearing failed"); return; } - if (plot_.set_consumer_feedback({interval_ns}) != Plot_Control_Error::none) + if (!plot_.set_consumer_feedback({interval_ns})) ::renderive::error::unexpected("validated low-latency feedback was rejected"); } void record_performance(std::chrono::steady_clock::time_point started, bool rendered) {