错误处理机制纠正

This commit is contained in:
2026-08-16 03:34:17 +08:00
parent 893be8d2c6
commit b48a70515f
91 changed files with 1983 additions and 1516 deletions
+2 -4
View File
@@ -49,11 +49,9 @@ if (RENDERIVE_BUILD_TESTS)
continue()
endif ()
file(RELATIVE_PATH Renderive_render_2D_test_name "${Renderive_render_2D_test_dir}" "${Renderive_render_2D_test_source}")
string(MD5 Renderive_render_2D_test_hash "${Renderive_render_2D_test_name}")
string(SUBSTRING "${Renderive_render_2D_test_hash}" 0 8 Renderive_render_2D_test_hash)
get_filename_component(Renderive_render_2D_test_name "${Renderive_render_2D_test_source}" NAME_WE)
string(REGEX REPLACE "\\.[^.]+$" "" Renderive_render_2D_test_name "${Renderive_render_2D_test_name}")
string(MAKE_C_IDENTIFIER "${Renderive_render_2D_test_name}" Renderive_render_2D_test_name)
set(Renderive_render_2D_test_target "Renderive_render_2D_${Renderive_render_2D_test_name}_${Renderive_render_2D_test_hash}")
set(Renderive_render_2D_test_target "Renderive_render_2D_${Renderive_render_2D_test_name}")
add_executable("${Renderive_render_2D_test_target}" "${Renderive_render_2D_test_source}")
target_link_libraries("${Renderive_render_2D_test_target}" PRIVATE Renderive_render_2D GTest::gtest_main)
renderive_stage_kernel_runtime("${Renderive_render_2D_test_target}")
+5 -5
View File
@@ -139,16 +139,16 @@ protected:
private:
template <class State_Type>
using State_Storage =
Double_State_Storage<State_Type, Atomic_Spin_Mutex, State_Observer>;
Published_State_Storage<State_Type, Atomic_Spin_Mutex, State_Observer>;
template <class State_Type>
[[nodiscard]] State_Storage<State_Type>& state_storage() noexcept {
return dynamic_cast<State_Storage<State_Type>&>(
this->template d_func<::renderive::Renderable::Impl>());
return static_cast<State_Storage<State_Type>&>(
*this->template d_func<::renderive::Renderable::Impl>().state_strategy);
}
template <class State_Type>
[[nodiscard]] const State_Storage<State_Type>& state_storage() const noexcept {
return dynamic_cast<const State_Storage<State_Type>&>(
this->template d_func<::renderive::Renderable::Impl>());
return static_cast<const State_Storage<State_Type>&>(
*this->template d_func<::renderive::Renderable::Impl>().state_strategy);
}
};
}
@@ -15,7 +15,7 @@ struct Selection_Interaction {
PointF selection_start{};
PointF selection_current{};
};
using Selection_Interaction_State = Double_State_Storage<Selection_Interaction>;
using Selection_Interaction_State = Published_State_Storage<Selection_Interaction>;
RectF axis_content_rect(const Axis_Transform& first, const Axis_Transform& second) {
return mapped_rect(first, second, first.coordinate_range, second.coordinate_range);
}
+1 -1
View File
@@ -16,7 +16,7 @@ struct Spectrum_Interaction {
int selected_marker = -1;
Hover_Tooltip_Runtime tooltip;
};
using Spectrum_Interaction_State = Double_State_Storage<Spectrum_Interaction>;
using Spectrum_Interaction_State = Published_State_Storage<Spectrum_Interaction>;
struct Prepared_Curve {
std::vector<PointF> points;
std::vector<PointF> fill;
+1 -1
View File
@@ -20,7 +20,7 @@ struct Waterfall_Interaction {
Hover_Tooltip_Runtime tooltip;
};
using Waterfall_History = Plottable_History_Real_Time_Data<Waterfall_Row, std::deque<Waterfall_Row>>;
using Waterfall_Interaction_State = Double_State_Storage<Waterfall_Interaction>;
using Waterfall_Interaction_State = Published_State_Storage<Waterfall_Interaction>;
struct Waterfall_Prepare_Buffer {
::renderive::Waterfall::Properties properties;
std::deque<Waterfall_Row> rows;
+6 -24
View File
@@ -1,10 +1,7 @@
#include "Renderable.h"
#include "Renderable_p.h"
#include "../render/Blend2D_Cache.h"
#include <renderive/scene/base/Render_Contexts.hpp>
namespace renderive {
namespace detail {
namespace {
@@ -12,49 +9,38 @@ struct Renderable_Group final : Renderable {
using Renderable::Renderable;
};
}
renderive_Owner<Renderable> make_renderable_group(bool cache_enabled) {
return renderive_Owner<Renderable_Group>::make(cache_enabled);
}
} // namespace detail
Renderable::Renderable(bool cache_enabled)
: render_base(std::make_unique<Impl>(), {.cache_enabled = cache_enabled}) {}
Renderable::~Renderable() = default;
Renderable_Cache_Mode Renderable::get_cache_mode() const noexcept {
return d_func<Impl>().configuration.load(std::memory_order_acquire).cache_enabled
? Renderable_Cache_Mode::Local_Pixel
: Renderable_Cache_Mode::Direct;
}
void Renderable::set_cache_mode(Renderable_Cache_Mode mode) {
d_func().set_configuration(
{.cache_enabled = mode == Renderable_Cache_Mode::Local_Pixel});
}
Renderable_Observation Renderable::observation() const noexcept {
const auto& d = d_func<Impl>();
std::lock_guard lock(d.observation_mutex);
return d.observation;
}
void Renderable::dispatch_event(const Event& event) {
if (auto* handler =
dynamic_cast<detail::Renderable_Event_Handler*>(&d_func<Impl>()))
dynamic_cast<detail::Renderable_Event_Handler*>(&d_func<Impl>()))
handler->handle_event(event);
}
Renderable& Renderable::Impl::renderable() noexcept {
return static_cast<Renderable&>(owner());
}
void Renderable::Impl::prepare_frame(const Prepare_Render_Context&) {}
void Renderable::Impl::paint(detail::Painter&,
const Paint_Render_Context&) {}
void Renderable::Impl::build_paint_graph(
Renderable_Graph_Builder& builder) {
const auto paint_task = add_paint_task(
@@ -65,18 +51,15 @@ void Renderable::Impl::build_paint_graph(
});
builder.precede(builder.find("prepare"), paint_task);
}
void Renderable::Impl::prepare(const Prepare_Render_Context& context) {
prepare_frame(context);
}
Renderable_Graph_Builder::Task Renderable::Impl::add_prepare_task(
Renderable_Graph_Builder& builder, std::string logical_key,
std::string name, Prepare_Task_Function function) {
return builder.emplace(std::move(logical_key), std::move(name),
std::move(function));
}
Renderable_Graph_Builder::Task Renderable::Impl::add_paint_task(
Renderable_Graph_Builder& builder, std::string logical_key,
std::string name, Paint_Task_Function function) {
@@ -91,24 +74,24 @@ Renderable_Graph_Builder::Task Renderable::Impl::add_paint_task(
if (!cache)
return;
detail::Painter painter(
*cache, {context.frame.viewport.width,
context.frame.viewport.height});
*cache, {
context.frame.viewport.width,
context.frame.viewport.height
});
if (painter)
function(painter, context);
});
}
void Renderable::Impl::observe_state(
Renderable_Observer_Event event, std::uint64_t time_ns,
std::uint64_t cache_update_count,
std::uint64_t publish_count) noexcept {
std::uint64_t publish_count) {
std::lock_guard lock(observation_mutex);
observation.event = event;
observation.event_time_ns = time_ns;
observation.cache_update_count = cache_update_count;
observation.publish_count = publish_count;
}
void Renderable::Impl::observe_data(
std::uint64_t revision, std::size_t item_count,
std::size_t auxiliary_item_count) noexcept {
@@ -117,5 +100,4 @@ void Renderable::Impl::observe_data(
observation.item_count = item_count;
observation.auxiliary_item_count = auxiliary_item_count;
}
} // namespace renderive
+1 -1
View File
@@ -65,7 +65,7 @@ struct Renderable_State_Observer {
static constexpr bool enabled = true;
explicit Renderable_State_Observer(Renderable* renderable) noexcept : renderable_(renderable) {}
template <class Observation>
void observe(const Observation& observation) noexcept {
void observe(const Observation& observation) {
renderable_->report_state_observation(observation);
}
private:
@@ -56,7 +56,7 @@ struct Renderable::Impl
struct Observer : renderable_inheritance::Observer_Root<Impl> {
static void handle(
Impl& impl,
const detail::Renderable_Event_View& value) noexcept {
const detail::Renderable_Event_View& value) {
impl.observe_state(value.event, value.time_ns,
value.cache_update_count,
value.publish_count);
@@ -90,7 +90,7 @@ private:
[[nodiscard]] Renderable& renderable() noexcept;
void observe_state(Renderable_Observer_Event event, std::uint64_t time_ns,
std::uint64_t cache_update_count,
std::uint64_t publish_count) noexcept;
std::uint64_t publish_count);
mutable std::mutex observation_mutex;
Renderable_Observation observation;
friend class Renderable;
+10 -12
View File
@@ -76,8 +76,8 @@ Scene_2D_Base::Attach_Builder Plot_Scene::attach_builder() {
return static_cast<Scene_2D_Base&>(render_scene()).attach_builder();
}
void Plot_Scene::edit_renderables(Scene_2D_Base::Renderable_Edit edit) {
static_cast<Scene_2D_Base&>(render_scene()).edit_renderables(std::move(edit));
Scene_Base::Edit_Operation Plot_Scene::edit_renderables(Scene_2D_Base::Renderable_Edit edit) {
return static_cast<Scene_2D_Base&>(render_scene()).edit_renderables(std::move(edit));
}
Scene_Base& Plot_Scene::render_scene() noexcept {
@@ -146,24 +146,22 @@ bool Plot_Scene::view_active() const noexcept {
void Plot_Scene::request_redraw() noexcept { render_scene().notify_model_dirty(); }
void Plot_Scene::set_max_render_fps(double fps) {
impl_->apply([fps](auto& scene) { scene.set_max_render_fps(fps); });
Plot_Control_Error Plot_Scene::set_max_render_fps(double fps) {
return impl_->apply([fps](auto& scene) { return scene.set_max_render_fps(fps); });
}
void Plot_Scene::clear_max_render_fps() {
impl_->apply([](auto& scene) { scene.clear_max_render_fps(); });
Plot_Control_Error Plot_Scene::clear_max_render_fps() {
return impl_->apply([](auto& scene) { return scene.clear_max_render_fps(); });
}
double Plot_Scene::max_render_fps() const noexcept {
return impl_->apply([](const auto& scene) { return scene.max_render_fps(); });
}
void Plot_Scene::set_consumer_feedback(Frame_Consumer_Feedback feedback) {
impl_->apply([feedback](auto& scene) { scene.set_consumer_feedback(feedback); });
Plot_Control_Error Plot_Scene::set_consumer_feedback(Frame_Consumer_Feedback feedback) {
return impl_->apply([feedback](auto& scene) { return scene.set_consumer_feedback(feedback); });
}
void Plot_Scene::clear_consumer_feedback() {
impl_->apply([](auto& scene) { scene.clear_consumer_feedback(); });
Plot_Control_Error Plot_Scene::clear_consumer_feedback() {
return impl_->apply([](auto& scene) { return scene.clear_consumer_feedback(); });
}
Plot_Frame_Mode Plot_Scene::frame_mode() const noexcept { return impl_->mode; }
+11 -5
View File
@@ -21,6 +21,12 @@ 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
};
struct Presentation_Sink {
virtual ~Presentation_Sink() = default;
@@ -40,7 +46,7 @@ public:
void init();
[[nodiscard]] ::renderive_Owner<Renderable> root_renderable() const;
[[nodiscard]] Scene_2D_Base::Attach_Builder attach_builder();
void edit_renderables(Scene_2D_Base::Renderable_Edit edit);
[[nodiscard]] Scene_Base::Edit_Operation edit_renderables(Scene_2D_Base::Renderable_Edit edit);
[[nodiscard]] Scene_Base& render_scene() noexcept;
[[nodiscard]] const Scene_Base& render_scene() const noexcept;
@@ -62,11 +68,11 @@ public:
[[nodiscard]] bool view_active() const noexcept;
void request_redraw() noexcept;
void set_max_render_fps(double fps);
void clear_max_render_fps();
[[nodiscard]] Plot_Control_Error set_max_render_fps(double fps);
[[nodiscard]] Plot_Control_Error clear_max_render_fps();
[[nodiscard]] double max_render_fps() const noexcept;
void set_consumer_feedback(Frame_Consumer_Feedback feedback);
void clear_consumer_feedback();
[[nodiscard]] Plot_Control_Error set_consumer_feedback(Frame_Consumer_Feedback feedback);
[[nodiscard]] Plot_Control_Error clear_consumer_feedback();
[[nodiscard]] Plot_Frame_Mode frame_mode() const noexcept;
[[nodiscard]] Plot_Frame_Status frame_status() const;
+6 -3
View File
@@ -8,7 +8,7 @@
#include "renderive/base/observer/Observer.hpp"
#include "renderive/frame_control/Frame_Control.hpp"
#include "renderive/renderable/color/Concepts.hpp"
#include "renderive/state/Double_State_Storage.hpp"
#include "renderive/state/Published_State_Storage.hpp"
#include "renderive/scene/base/Scene_Base.hpp"
#include "renderive/scene/base/Frame_Viewport.hpp"
struct Scene2D_Frame_Data : Abstract_Frame {};
@@ -24,12 +24,12 @@ concept Scene2D_Frame_Control_Constructible = std::constructible_from<Frame_Cont
template <class Strategy = Low_Latency_Strategy<Scene2D_Frame_Data>, Color_Cache_Type Cache = Recording_Color_Cache, class State = Scene2D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>>
requires Frame_Control_Strategy_For<Strategy, Scene2D_Frame_Data> && Scene2D_State_Value<State>
class Scene2D_Context : public Scene_2D_Base,
public Double_State_Storage<State, Atomic_Spin_Mutex,
public Published_State_Storage<State, Atomic_Spin_Mutex,
State_Observer>,
public Scene_Compositor {
public:
using Scene_State_Strategy =
Double_State_Storage<State, Atomic_Spin_Mutex, State_Observer>;
Published_State_Storage<State, Atomic_Spin_Mutex, State_Observer>;
using Frame_Control = Strategy;
using Frame = typename Frame_Control::Frame;
using Painter_Lease = typename Frame_Control::Painter_Lease;
@@ -86,6 +86,9 @@ protected:
if (context.renderable && context.renderable->visible && context.color_cache)
final_color_cache_.composite(*context.color_cache);
}
void publish_scene_state() override {
this->Scene_State_Strategy::publish();
}
std::uint64_t acquire_scene_state() override {
return this->Scene_State_Strategy::state_revision();
}
@@ -48,7 +48,7 @@ struct Frame_Observer {
static constexpr bool enabled = true;
std::shared_ptr<Frame_Observer_Data> data;
template <class Observation>
void observe(const Observation& observation) noexcept {
void observe(const Observation& observation) {
std::lock_guard lock(data->mutex);
data->last_event = static_cast<std::uint32_t>(observation.event);
++data->observation_count;
@@ -192,9 +192,7 @@ struct Plot_Scene_Model final : ::Scene2D_Context<Frame_Control, detail::Blend2D
auto render_frame = this->frame_control.acquire_renderer();
if (!render_frame)
return false;
this->render(*render_frame);
this->wait_for_render();
return true;
return Scene_Base::render(*render_frame) == Scene_Render_Error::none;
}
[[nodiscard]] bool discard_pending_frame() {
if constexpr (Mode == Plot_Frame_Mode::Playback)
@@ -259,37 +257,44 @@ struct Plot_Scene_Model final : ::Scene2D_Context<Frame_Control, detail::Blend2D
[[nodiscard]] bool view_active() const noexcept {
return active_.load(std::memory_order_acquire);
}
void set_max_render_fps(double fps) {
[[nodiscard]] Plot_Control_Error set_max_render_fps(double fps) {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency) {
throw std::logic_error("maximum render FPS is only available in low-latency mode");
}
else {
if (!std::isfinite(fps) || fps <= 0.0)
throw std::invalid_argument("maximum render FPS must be finite and positive");
this->frame_control.set_frequency_hz(fps);
return Plot_Control_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;
this->notify_model_dirty();
return Plot_Control_Error::none;
}
}
void clear_max_render_fps() {
[[nodiscard]] Plot_Control_Error clear_max_render_fps() {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency) {
throw std::logic_error("maximum render FPS is only available in low-latency mode");
}
else {
return Plot_Control_Error::unsupported_frame_mode;
} else {
this->frame_control.clear_frequency_limit();
this->notify_model_dirty();
return Plot_Control_Error::none;
}
}
void set_consumer_feedback(Frame_Consumer_Feedback feedback) {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency)
throw std::logic_error("consumer feedback is only available in low-latency mode");
else
this->frame_control.set_consumer_feedback(feedback);
[[nodiscard]] Plot_Control_Error set_consumer_feedback(Frame_Consumer_Feedback feedback) {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency) {
return Plot_Control_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;
}
}
void clear_consumer_feedback() {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency)
throw std::logic_error("consumer feedback is only available in low-latency mode");
else
[[nodiscard]] Plot_Control_Error clear_consumer_feedback() {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency) {
return Plot_Control_Error::unsupported_frame_mode;
} else {
this->frame_control.clear_consumer_feedback();
return Plot_Control_Error::none;
}
}
[[nodiscard]] double max_render_fps() const noexcept {
if constexpr (Mode == Plot_Frame_Mode::Low_Latency)
@@ -3,7 +3,6 @@
#include <algorithm>
#include <array>
#include <limits>
#include <stdexcept>
namespace renderive {
namespace {
TEST(Renderive_Core2_Frame_Pipeline, ManualLifecycleSeparatesPrepareRefreshAndRender) {
@@ -39,7 +38,7 @@ TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKern
Plot_Scene plot;
plot.init();
plot.set_viewport_size({96, 54});
plot.set_max_render_fps(1'000'000'000.0);
EXPECT_EQ(plot.set_max_render_fps(1'000'000'000.0), Plot_Control_Error::none);
ASSERT_TRUE(plot.prepare_frame());
auto snapshot = plot.frame_status();
EXPECT_EQ(snapshot.last_event, "published");
@@ -72,8 +71,8 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2
Plot_Scene plot;
plot.init();
plot.set_viewport_size({96, 54});
plot.set_max_render_fps(100.0);
plot.set_consumer_feedback({40'000'000});
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_TRUE(plot.prepare_frame());
ASSERT_TRUE(plot.render_prepared_frame());
const auto snapshot = plot.frame_status();
@@ -93,8 +92,8 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyLimitsCanBeClearedIndependently)
Plot_Scene plot;
plot.init();
plot.set_viewport_size({96, 54});
plot.set_max_render_fps(100.0);
plot.set_consumer_feedback({40'000'000});
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_TRUE(plot.prepare_frame());
@@ -250,18 +249,18 @@ TEST(Renderive_Core2_Frame_Pipeline, EveryModeHonorsActiveDirtyAndForceRenderGat
}
TEST(Renderive_Core2_Frame_Pipeline, FrequencyConfigurationRejectsInvalidValuesAndWrongModes) {
Plot_Scene low_latency;
EXPECT_THROW(low_latency.set_max_render_fps(0.0), std::invalid_argument);
EXPECT_THROW(low_latency.set_max_render_fps(-1.0), std::invalid_argument);
EXPECT_THROW(low_latency.set_max_render_fps(std::numeric_limits<double>::quiet_NaN()),
std::invalid_argument);
EXPECT_THROW(low_latency.set_max_render_fps(std::numeric_limits<double>::infinity()),
std::invalid_argument);
low_latency.set_max_render_fps(1'000.0);
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_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_THROW(manual.set_max_render_fps(60.0), std::logic_error);
EXPECT_THROW(playback.set_max_render_fps(60.0), std::logic_error);
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_FALSE(playback.refresh_manual_frame());
EXPECT_FALSE(playback.discard_pending_frame());
}
@@ -61,7 +61,7 @@ TEST(Renderive_Core2, RootIdentityAndRefreshDiagnosticsComeFromKernelState) {
EXPECT_EQ(plot.root_renderable(), root);
plot.set_viewport_size({32, 24});
plot.activate_view();
plot.set_max_render_fps(144.0);
EXPECT_EQ(plot.set_max_render_fps(144.0), Plot_Control_Error::none);
ASSERT_TRUE(plot.render_frame());
const auto diagnostics = plot.diagnostics();
EXPECT_DOUBLE_EQ(diagnostics.refresh.frequency_hz, 144.0);
@@ -296,7 +296,7 @@ TEST(Renderive_Core2, PerformanceOverlayConsumesKernelFrameDiagnostics) {
plot.init();
plot.set_viewport_size({160, 90});
plot.activate_view();
plot.set_max_render_fps(120.0);
EXPECT_EQ(plot.set_max_render_fps(120.0), Plot_Control_Error::none);
Performance_Overlay_Options options;
options.log.enabled = false;
const auto overlay = attach_performance_overlay(plot, options);
@@ -1018,7 +1018,9 @@ TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSe
constexpr std::array partition_counts{64, 16, 1};
std::uint64_t tick{1};
for (std::size_t round = 0; round < 3; ++round) {
const Capture_Session_Id session_id = scene.render_scene().capture_frames(8);
const auto request = scene.render_scene().capture_frames(8);
ASSERT_TRUE(request);
const Capture_Session_Id session_id = request.session_id;
for (std::size_t frame = 0; frame < 8; ++frame) {
if (frame % 2 == 0) {
waterfall->set<&Waterfall::Properties::partition_count>(