This commit is contained in:
2026-08-16 21:18:28 +08:00
parent 9d544c7ba9
commit 3153f7d64f
15 changed files with 387 additions and 200 deletions
+67 -7
View File
@@ -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<Frame, Request_Frame_Result> 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<T, Xxx_Result>
```
* 没有额外正常返回值,返回码本身即可完整表达结果:
```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;第三方结果进入系统后重新按同一规则分类。**
+2 -1
View File
@@ -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
+6 -3
View File
@@ -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<void, Plot_Set_Max_Render_Fps_Error>
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 {
+2 -1
View File
@@ -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<void, Plot_Set_Max_Render_Fps_Error>
set_max_render_fps(double fps);
[[nodiscard]] Low_Latency_Diagnostics diagnostics() const;
[[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const;
};
+4 -3
View File
@@ -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();
});
}
+1
View File
@@ -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@)
+6 -2
View File
@@ -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
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
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 ()
+13 -8
View File
@@ -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<void, Plot_Prepare_Frame_Error> Plot_Scene::prepare_frame() {
return impl_->apply([](auto& scene) { return scene.prepare_frame(); });
}
Plot_Render_Result Plot_Scene::refresh_manual_frame() {
tl::expected<void, Plot_Refresh_Manual_Frame_Error> 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<void, Plot_Render_Prepared_Frame_Error> 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_Render_Status, Plot_Render_Frame_Error>
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<void, Plot_Set_Max_Render_Fps_Error>
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<void, Plot_Clear_Max_Render_Fps_Error>
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<void, Plot_Set_Consumer_Feedback_Error>
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<void, Plot_Clear_Consumer_Feedback_Error>
Plot_Scene::clear_consumer_feedback() {
return impl_->apply([](auto& scene) { return scene.clear_consumer_feedback(); });
}
+45 -16
View File
@@ -10,6 +10,7 @@
#include <renderive/scene/base/Scene_Base.hpp>
#include <functional>
#include <tl/expected.hpp>
#include <memory>
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<void, Plot_Prepare_Frame_Error> prepare_frame();
[[nodiscard]] tl::expected<void, Plot_Refresh_Manual_Frame_Error> refresh_manual_frame();
[[nodiscard]] tl::expected<void, Plot_Render_Prepared_Frame_Error> render_prepared_frame();
[[nodiscard]] bool discard_pending_frame();
[[nodiscard]] Plot_Render_Result render_frame(bool force = false);
[[nodiscard]] tl::expected<Plot_Render_Status, Plot_Render_Frame_Error>
render_frame(bool force = false);
void with_frame(const std::function<void(Image_View)>& 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<void, Plot_Set_Max_Render_Fps_Error>
set_max_render_fps(double fps);
[[nodiscard]] tl::expected<void, Plot_Clear_Max_Render_Fps_Error> 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<void, Plot_Set_Consumer_Feedback_Error>
set_consumer_feedback(Frame_Consumer_Feedback feedback);
[[nodiscard]] tl::expected<void, Plot_Clear_Consumer_Feedback_Error>
clear_consumer_feedback();
[[nodiscard]] Plot_Frame_Mode frame_mode() const noexcept;
[[nodiscard]] Plot_Frame_Status frame_status() const;
@@ -178,36 +178,43 @@ struct Plot_Scene_Model final : ::Scene2D_Context<Frame_Control, detail::Blend2D
}
}
}
[[nodiscard]] Plot_Render_Result prepare_frame() {
[[nodiscard]] tl::expected<void, Plot_Prepare_Frame_Error> 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<void, Plot_Refresh_Manual_Frame_Error>
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<void, Plot_Render_Prepared_Frame_Error>
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<std::logic_error>("unknown scene render error");
}
@@ -217,26 +224,55 @@ struct Plot_Scene_Model final : ::Scene2D_Context<Frame_Control, detail::Blend2D
else
return this->frame_control.discard_pending_frame();
}
[[nodiscard]] Plot_Render_Result render_frame(bool force = false) {
[[nodiscard]] tl::expected<Plot_Render_Status, Plot_Render_Frame_Error>
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<std::logic_error>(
"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<std::logic_error>(
"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_Context<Frame_Control, detail::Blend2D
detail::record_performance_frame(*overlay, duration_ms, diagnostics(), this->renderable_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<void(Image_View)>& consumer) {
if (!consumer)
@@ -275,43 +311,57 @@ struct Plot_Scene_Model final : ::Scene2D_Context<Frame_Control, detail::Blend2D
[[nodiscard]] bool view_active() const noexcept {
return active_.load(std::memory_order_acquire);
}
[[nodiscard]] Plot_Control_Error set_max_render_fps(double fps) {
[[nodiscard]] tl::expected<void, Plot_Set_Max_Render_Fps_Error>
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<void, Plot_Clear_Max_Render_Fps_Error>
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<void, Plot_Set_Consumer_Feedback_Error>
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<void, Plot_Clear_Consumer_Feedback_Error>
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_Context<Frame_Control, detail::Blend2D
snapshot.produced_frame_count = state.prepared_frame_count;
snapshot.consumed_frame_count = state.render_count;
snapshot.dropped_frame_count = state.replaced_prepared_frame_count +
state.discarded_prepared_frame_count;
state.discarded_prepared_frame_count;
snapshot.failed_operation_count = state.failed_refresh_count;
snapshot.pending_frame_count = state.pending_frame ? 1U : 0U;
}
@@ -376,7 +426,7 @@ struct Plot_Scene_Model final : ::Scene2D_Context<Frame_Control, detail::Blend2D
snapshot.produced_frame_count = counters.published_frame_count;
snapshot.consumed_frame_count = state.completed_lifecycle_count;
snapshot.dropped_frame_count = counters.abandoned_frame_count +
counters.manually_discarded_frame_count;
counters.manually_discarded_frame_count;
snapshot.failed_operation_count = counters.swap_failure_count;
snapshot.pending_frame_count = this->frame_control.pending_frame_count();
snapshot.limit_state = detail::limit_state_name(static_cast<std::uint32_t>(state.limit_state));
@@ -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<double>::quiet_NaN()),
Plot_Control_Error::invalid_max_render_fps);
EXPECT_EQ(low_latency.set_max_render_fps(std::numeric_limits<double>::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<double>::quiet_NaN()).error(),
Plot_Set_Max_Render_Fps_Error::invalid_fps);
EXPECT_EQ(low_latency.set_max_render_fps(std::numeric_limits<double>::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
+33 -33
View File
@@ -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<double>(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<int>(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);
@@ -91,10 +91,11 @@ struct Type_Descriptor<renderive::web::Gallery_Session_Control<Scene_Type>> {
[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<renderive::web::Gallery_Session_Control<Scene_Type>> {
},
[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);
}),
+8 -3
View File
@@ -1,11 +1,13 @@
#include "Web_Plot_Session.h"
#include "Pixel_Frame.h"
#include "render_2D/export.h"
#include <renderive/error/Error_Policy.hpp>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <mutex>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
@@ -64,7 +66,9 @@ struct Web_Plot_Session::Impl {
Impl() {
plot.init();
plot.set_background_color({3, 7, 18, 255});
static_cast<void>(plot.set_max_render_fps(30.0));
if (!plot.set_max_render_fps(30.0))
::renderive::error::unexpected<std::logic_error>(
"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();
+42 -25
View File
@@ -29,23 +29,36 @@
#include <vector>
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<Plot_Render_Status, Plot_Render_Frame_Error>& 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<std::logic_error>("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<std::logic_error>("unknown plot render error");
}
template <class Update>
void update_axis_state(const renderive_Owner<Abs_Axis>& 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<void>(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<std::logic_error>(
"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<std::string> 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<std::logic_error>("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<std::logic_error>("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<std::logic_error>("validated low-latency feedback was rejected");
}
void record_performance(std::chrono::steady_clock::time_point started, bool rendered) {