diff --git a/Kernel/src/renderive/renderable/Renderable_Build.hpp b/Kernel/src/renderive/renderable/Renderable_Build.hpp new file mode 100644 index 0000000..d8fae40 --- /dev/null +++ b/Kernel/src/renderive/renderable/Renderable_Build.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "renderive/renderable/base/Renderive_Owner.hpp" +#include +#include +#include +#include + +class Renderable_Base; + +namespace renderive { +struct Renderable_Build_Relationship { + std::weak_ptr child; + std::weak_ptr parent; +}; + +struct Renderable_Build_Topology { + std::vector dependency; + std::vector display; +}; + +namespace detail { +class Renderable_Build_Access { +public: + static void set_object_name(Renderable_Base& renderable, std::string name); + static void set_topology(Renderable_Base& renderable, + Renderable_Build_Topology topology); + [[nodiscard]] static const Renderable_Build_Topology& topology( + const Renderable_Base& renderable) noexcept; + static void complete(Renderable_Base& renderable) noexcept; +}; + +template +[[nodiscard]] Renderable_Build_Relationship build_relationship( + const renderive_Owner& child, + const renderive_Owner& parent) { + return {child.share(), parent.share()}; +} +} // namespace detail +} // namespace renderive diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp index a8c7686..e2dfb90 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp @@ -1,5 +1,6 @@ #include #include "Renderable_Base_p.hpp" +#include "renderive/renderable/Renderable_Build.hpp" #include #include #include @@ -223,15 +224,33 @@ bool Renderable_Base::is_visible() const noexcept { return d_func().visible.load(std::memory_order_acquire); } std::string Renderable_Base::object_name() const { - const auto& implementation = d_func(); - std::lock_guard lock(implementation.metadata_mutex); - return implementation.object_name; + return d_func().object_name; } -void Renderable_Base::set_object_name(std::string name) { - auto& implementation = d_func(); - std::lock_guard lock(implementation.metadata_mutex); +void renderive::detail::Renderable_Build_Access::set_object_name( + Renderable_Base& renderable, std::string name) { + auto& implementation = renderable.d_func(); + if (implementation.real_time_data_state->scene_lifetime) + ::renderive::error::unexpected( + "object_name is immutable after scene construction"); implementation.object_name = std::move(name); } +void renderive::detail::Renderable_Build_Access::set_topology( + Renderable_Base& renderable, renderive::Renderable_Build_Topology topology) { + auto& implementation = renderable.d_func(); + if (implementation.real_time_data_state->scene_lifetime) + ::renderive::error::unexpected( + "renderable build topology is immutable after scene construction"); + implementation.build_topology = std::move(topology); +} +const renderive::Renderable_Build_Topology& +renderive::detail::Renderable_Build_Access::topology( + const Renderable_Base& renderable) noexcept { + return renderable.d_func().build_topology; +} +void renderive::detail::Renderable_Build_Access::complete( + Renderable_Base& renderable) noexcept { + renderable.d_func().build_topology = {}; +} void Renderable_Base::set_visible(bool visible) { d_func().set_visible(visible); } diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.hpp b/Kernel/src/renderive/renderable/base/Renderable_Base.hpp index c5dc76c..2d18d46 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.hpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.hpp @@ -14,13 +14,15 @@ class Scene_2D_Base; namespace renderive::inheritance { struct Observation_Source; } +namespace renderive::detail { +class Renderable_Build_Access; +} class Renderable_Base { public: virtual ~Renderable_Base(); [[nodiscard]] Renderable_Id renderable_id() const noexcept; [[nodiscard]] bool is_visible() const noexcept; [[nodiscard]] std::string object_name() const; - void set_object_name(std::string name); void set_visible(bool visible); void discard_stale_frame_on_latest_data_update(bool enabled) noexcept; protected: @@ -53,5 +55,6 @@ private: friend class Real_Time_Data_Binding; friend class Scene_Base; friend class Scene_2D_Base; + friend class renderive::detail::Renderable_Build_Access; std::unique_ptr d_ptr; }; diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp b/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp index f886551..d3c2169 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base_p.hpp @@ -1,5 +1,6 @@ #pragma once #include "Renderable_Base.hpp" +#include "renderive/renderable/Renderable_Build.hpp" #include "renderive/inheritance/Inheritance.hpp" #include "renderive/inheritance/Observation.hpp" #include @@ -76,8 +77,8 @@ public: const Render_Node_Id composite_node_id; std::atomic configuration; std::atomic visible{true}; - mutable std::mutex metadata_mutex; std::string object_name; + renderive::Renderable_Build_Topology build_topology; std::mutex render_graph_mutex; std::shared_ptr render_graph; std::unordered_map node_identities; diff --git a/Kernel/src/renderive/scene/Scene_Build_Result.hpp b/Kernel/src/renderive/scene/Scene_Build_Result.hpp new file mode 100644 index 0000000..b157022 --- /dev/null +++ b/Kernel/src/renderive/scene/Scene_Build_Result.hpp @@ -0,0 +1,13 @@ +#pragma once +#include + +enum class Scene_Build_Result : std::uint8_t { + null_renderable, + foreign_renderable, + dependency_endpoint_missing, + dependency_self_reference, + dependency_cycle, + display_endpoint_missing, + display_self_reference, + display_cycle +}; diff --git a/Kernel/src/renderive/scene/Scene_Builder.hpp b/Kernel/src/renderive/scene/Scene_Builder.hpp index ec40061..cfca31e 100644 --- a/Kernel/src/renderive/scene/Scene_Builder.hpp +++ b/Kernel/src/renderive/scene/Scene_Builder.hpp @@ -1,52 +1,163 @@ #pragma once +#include "renderive/base/adapter/Expected.hpp" #include "renderive/base/property/Concepts.hpp" #include "renderive/base/property/Validators.hpp" +#include "renderive/error/Error_Policy.hpp" +#include "renderive/renderable/base/Renderable_Base.hpp" +#include "renderive/renderable/base/Renderive_Owner.hpp" #include "renderive/scene/Inheritance.hpp" +#include "renderive/scene/Scene_Build_Result.hpp" +#include "renderive/scene/base/Scene_Base.hpp" +#include #include #include +#include #include #include #include +#include + +namespace renderive::scene::detail { +struct Scene_Build_Access { + [[nodiscard]] static auto build_initial_topology( + Scene_Base& scene, + const std::vector& renderables) + -> Expected { + return scene.build_initial_topology(renderables); + } +}; +} // namespace renderive::scene::detail + namespace renderive::scene { -template Validator_Type = No_Property_Validator> +template Validator_Type = + No_Property_Validator> struct Scene_Builder { using Product = Product_Type; using Properties = Properties_Type; using Validator = Validator_Type; using Self = Scene_Builder; - Scene_Builder() requires std::default_initializable : constructor_(make_constructor()) {} - template requires (sizeof...(Args) > 0) && std::default_initializable - explicit Scene_Builder(Args&&... args) : constructor_(make_constructor(std::forward(args)...)) {} - template Value> + using Renderable = Scene_Base::Renderable; + + + Scene_Builder() requires std::default_initializable + : constructor_(make_constructor()) {} + + template + requires (sizeof...(Args) > 0) && std::default_initializable + explicit Scene_Builder(Args&&... args) { + (collect_constructor_renderable(args), ...); + constructor_ = make_constructor(std::forward(args)...); + } + + template Value> Self& set(Value&& value) { properties_.*Member = std::forward(value); return *this; } + template Configure> Self& configure(Configure&& configure) { std::invoke(std::forward(configure), properties_); return *this; } - [[nodiscard]] std::unique_ptr build() { + + Self& add_renderable(Renderable renderable) { + renderables_.push_back(std::move(renderable)); + return *this; + } + + template + requires std::derived_from + Self& add_renderable(const renderive_Owner& renderable) { + renderables_.emplace_back(renderable); + return *this; + } + + template + requires std::derived_from + Self& add_renderable(const std::shared_ptr& renderable) { + renderables_.emplace_back( + std::static_pointer_cast(renderable)); + return *this; + } + + Self& add_renderables(std::vector renderables) { + for (auto& renderable : renderables) + renderables_.push_back(std::move(renderable)); + return *this; + } + + [[nodiscard]] const Properties& properties_value() const noexcept { + return properties_; + } + + [[nodiscard]] auto build() + -> Expected, Scene_Build_Result> { + static_assert(std::derived_from); validator_(properties_); auto construct = std::exchange(constructor_, {}); - return construct(properties_); + if (!construct) + ::renderive::error::unexpected( + "scene builder can only build once"); + + auto result = construct(properties_); + if (!result) + ::renderive::error::unexpected( + "scene constructor returned null"); + + auto topology = detail::Scene_Build_Access::build_initial_topology( + *result, renderables_); + if (!topology) return unexpected(topology.error()); + return std::move(result); } + +protected: + [[nodiscard]] const std::vector& renderables() const noexcept { + return renderables_; + } + private: - using Constructor = std::function(const Properties&)>; + using Constructor = + std::function(const Properties&)>; + template static Constructor make_constructor(Args&&... args) { - auto stored = std::make_shared...>>(std::forward(args)...); - return [stored = std::move(stored)](const Properties& properties) mutable { + auto stored = + std::make_shared...>>( + std::forward(args)...); + return [stored = std::move(stored)]( + const Properties& properties) mutable { return std::apply( [&](auto&... values) { - return scene_inheritance::Builder_Access::make(properties, std::move(values)...); + return scene_inheritance::Builder_Access::make( + properties, std::move(values)...); }, *stored); }; } + + template + requires std::derived_from + void collect_constructor_renderable( + const renderive_Owner& renderable) { + add_renderable(renderable); + } + + template + requires std::derived_from + void collect_constructor_renderable( + const std::shared_ptr& renderable) { + add_renderable(renderable); + } + + template + void collect_constructor_renderable(const Value&) {} + Properties properties_{}; [[no_unique_address]] Validator validator_{}; Constructor constructor_; + std::vector renderables_; }; -} +} // namespace renderive::scene diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index bdfcf89..1a95c55 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -20,6 +20,7 @@ #include #include #include +#include "renderive/renderable/Renderable_Build.hpp" #include "renderive/renderable/Render_Frame_Completion.hpp" #include "renderive/renderable/Renderive_Id_Allocator.hpp" #include "renderive/renderable/base/Renderable_Base_p.hpp" @@ -354,6 +355,111 @@ Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource, Scene_Base::~Scene_Base() { d_func().shutdown(); } +auto Scene_Base::build_initial_topology( + const std::vector& renderables) + -> renderive::Expected { + std::unordered_set build_renderables; + build_renderables.reserve(renderables.size()); + for (const auto& renderable : renderables) { + if (!renderable) + return renderive::unexpected(Scene_Build_Result::null_renderable); + build_renderables.insert(renderable.get()); + } + + auto& implementation = d_func(); + { + std::lock_guard lock(implementation.task_mutex_); + if (implementation.runtime_started_) + ::renderive::error::unexpected( + "initial scene topology must be built before runtime starts"); + } + + const Scene_Edit_Error edit_error = implementation.execute_renderable_edit( + [&]() -> Scene_Edit_Error { + Renderable_Editor editor(*this); + for (const auto& renderable : renderables) { + if (const auto error = editor.attach(renderable); + error != Scene_Edit_Error::none) + return error; + } + + for (const auto& renderable : renderables) { + const auto& topology = + renderive::detail::Renderable_Build_Access::topology( + *renderable); + for (const auto& relationship : topology.dependency) { + auto child = relationship.child.lock(); + auto parent = relationship.parent.lock(); + if (!child || !parent || + !build_renderables.contains(child.get()) || + !build_renderables.contains(parent.get())) + return Scene_Edit_Error::dangling_dependency; + const auto error = editor.add_dependency_parent( + Renderable(std::move(child)), + Renderable(std::move(parent))); + if (error != Scene_Edit_Error::none) return error; + } + } + + for (const auto& renderable : renderables) { + const auto& topology = + renderive::detail::Renderable_Build_Access::topology( + *renderable); + for (const auto& relationship : topology.display) { + auto child = relationship.child.lock(); + auto parent = relationship.parent.lock(); + if (!child || !parent || + !build_renderables.contains(child.get()) || + !build_renderables.contains(parent.get())) + return Scene_Edit_Error::dangling_display; + auto* composition = implementation.composition(); + if (!composition) return Scene_Edit_Error::dangling_display; + const auto error = composition->add_parent( + child->renderable_id(), parent->renderable_id()); + if (error != Scene_Edit_Error::none) return error; + } + } + return Scene_Edit_Error::none; + }); + + if (edit_error == Scene_Edit_Error::none) { + for (const auto& renderable : renderables) + renderive::detail::Renderable_Build_Access::complete(*renderable); + return {}; + } + + switch (edit_error) { + case Scene_Edit_Error::null_renderable: + return renderive::unexpected(Scene_Build_Result::null_renderable); + case Scene_Edit_Error::foreign_renderable: + return renderive::unexpected(Scene_Build_Result::foreign_renderable); + case Scene_Edit_Error::dependency_self_reference: + return renderive::unexpected( + Scene_Build_Result::dependency_self_reference); + case Scene_Edit_Error::dependency_cycle: + return renderive::unexpected(Scene_Build_Result::dependency_cycle); + case Scene_Edit_Error::display_self_reference: + return renderive::unexpected( + Scene_Build_Result::display_self_reference); + case Scene_Edit_Error::display_cycle: + return renderive::unexpected(Scene_Build_Result::display_cycle); + case Scene_Edit_Error::dangling_dependency: + return renderive::unexpected( + Scene_Build_Result::dependency_endpoint_missing); + case Scene_Edit_Error::dangling_display: + return renderive::unexpected( + Scene_Build_Result::display_endpoint_missing); + case Scene_Edit_Error::none: + break; + case Scene_Edit_Error::empty_edit: + case Scene_Edit_Error::renderable_not_attached: + case Scene_Edit_Error::shutting_down: + ::renderive::error::unexpected( + "unexpected result while building initial scene topology"); + } + ::renderive::error::unexpected( + "unhandled initial scene topology result"); +} Scene_Base::Attach_Builder::Attach_Builder(Scene_Base& scene) : scene_(scene), task_lock_(scene.d_func().task_mutex_) { if (scene.d_func().runtime_started_) ::renderive::error::unexpected("scene attach builder is only available before runtime starts"); } @@ -1222,6 +1328,12 @@ std::shared_ptr Scene_Base::frame_control_str void Scene_2D_Base::Impl::attach(Renderable_Id id) { if (!display_resolver_.contains(id)) display_resolver_.attach(id); } +Scene_Edit_Error Scene_2D_Base::Impl::add_parent( + Renderable_Id child, Renderable_Id parent) { + const auto result = display_resolver_.add_parent(child, parent); + if (!result) return relationship_error(result.error, true); + return Scene_Edit_Error::none; +} bool Scene_2D_Base::Impl::cleanup() { for (const Renderable_Id id : display_resolver_.ids()) { if (renderables_.contains(id)) continue; diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index 1bfd2f6..f89d88c 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -16,6 +16,7 @@ #include #include #include +#include "renderive/base/adapter/Expected.hpp" #include "renderive/base/memory/Memory_Resource.hpp" #include "renderive/capture/Capture.hpp" #include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp" @@ -25,6 +26,7 @@ #include "renderive/render_graph/External_Operation.hpp" #include "renderive/render_graph/Render_Plan.hpp" #include "renderive/scene/Inheritance.hpp" +#include "renderive/scene/Scene_Build_Result.hpp" #include "renderive/scene/dependency/Dependency_Resolver.hpp" #include "Abstract_Frame.hpp" #include "Frame_Render_Snapshot.hpp" @@ -35,6 +37,9 @@ namespace renderive::inheritance { struct Observation_Source; } +namespace renderive::scene::detail { +struct Scene_Build_Access; +} class Color_Cache; enum class Scene_Render_Result : std::uint8_t { none, @@ -218,6 +223,10 @@ protected: return static_cast(*impl_); } private: + [[nodiscard]] auto build_initial_topology( + const std::vector& renderables) + -> renderive::Expected; + friend struct renderive::scene::detail::Scene_Build_Access; friend class Renderable_Base; friend class Scene_2D_Base; std::unique_ptr impl_; diff --git a/Kernel/src/renderive/scene/base/Scene_Base.inl b/Kernel/src/renderive/scene/base/Scene_Base.inl index 2040fa1..37bf647 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.inl +++ b/Kernel/src/renderive/scene/base/Scene_Base.inl @@ -25,6 +25,8 @@ public: struct Composition_Relationships { virtual ~Composition_Relationships() = default; virtual void attach(Renderable_Id id) = 0; + [[nodiscard]] virtual Scene_Edit_Error add_parent( + Renderable_Id child, Renderable_Id parent) = 0; [[nodiscard]] virtual bool cleanup() = 0; [[nodiscard]] virtual bool valid() const = 0; virtual renderive::scene::dependency::Resolution resolve() const = 0; @@ -175,6 +177,8 @@ struct Scene_2D_Base::Impl [[nodiscard]] Scene_Edit_Error clear_display_parent_locked( const Renderable& child); void attach(Renderable_Id id) override; + [[nodiscard]] Scene_Edit_Error add_parent( + Renderable_Id child, Renderable_Id parent) override; [[nodiscard]] bool cleanup() override; [[nodiscard]] bool valid() const override; [[nodiscard]] renderive::scene::dependency::Resolution resolve() const override; diff --git a/Qt/plot/Plot.cpp b/Qt/plot/Plot.cpp index d537da4..28a918f 100644 --- a/Qt/plot/Plot.cpp +++ b/Qt/plot/Plot.cpp @@ -4,6 +4,8 @@ #include "../bridge/Qt_Image_Adapter.h" #include "../bridge/Qt_Presentation_Sink.h" +#include + #include #include #include @@ -13,6 +15,7 @@ #include #include +#include namespace renderive { namespace { @@ -30,20 +33,28 @@ QColor to_qcolor(Color color) { return QColor(color.r, color.g, color.b, color.a); } +std::shared_ptr make_plot_strategy( + bool continuous_rendering) { + if (continuous_rendering) + return std::make_shared(); + return std::make_shared(); +} + } // namespace Abs_Plot_Private::Abs_Plot_Private(bool continuous_rendering) - : scene(std::make_unique()), continuous(continuous_rendering) {} + : scene_builder(std::make_unique( + make_plot_strategy(continuous_rendering))), + continuous(continuous_rendering) {} Abs_Plot_Private::~Abs_Plot_Private() = default; void Abs_Plot_Private::attach_widget(Abs_Plot* plot) { presentation_sink = std::make_shared(plot); - scene->set_presentation_sink(presentation_sink); timer = new QTimer(plot); timer->setTimerType(Qt::PreciseTimer); QObject::connect(timer, &QTimer::timeout, plot, [this] { - if (!scene->view_active()) + if (!scene || !scene->view_active()) return; const auto result = scene->render_frame(); if (!result && @@ -67,13 +78,26 @@ Abs_Plot::~Abs_Plot() { return; if (d->timer) d->timer->stop(); - d->scene->deactivate_view(); + if (d->scene) d->scene->deactivate_view(); delete d; d = nullptr; } void Abs_Plot::init() { - d->scene->init(); + if (d->scene) return; + if (!d->scene_builder) + ::renderive::error::unexpected( + "plot scene builder is unavailable"); + d->scene_builder + ->set<&Render_Scene_2D::Properties::viewport>( + Qt_Event_Adapter::to_size(size())); + auto built_scene = d->scene_builder->build(); + if (!built_scene) + ::renderive::error::unexpected( + "plot scene topology validation failed"); + d->scene = std::move(*built_scene); + d->scene_builder.reset(); + d->scene->set_presentation_sink(d->presentation_sink); } Render_Scene_2D* Abs_Plot::scene() const { @@ -81,18 +105,40 @@ Render_Scene_2D* Abs_Plot::scene() const { } renderive_Owner Abs_Plot::root_renderable() const { - return d->scene->root_renderable(); + if (d->scene) return d->scene->root_renderable(); + return d->scene_builder ? d->scene_builder->root_renderable() + : renderive_Owner{}; +} + +void Abs_Plot::add_renderable(renderive_Owner renderable) { + if (!d->scene_builder || d->scene) + ::renderive::error::unexpected( + "plot renderables must be added before init"); + d->scene_builder->add_renderable(std::move(renderable)); } QColor Abs_Plot::background_color() const { - return to_qcolor(d->scene->background_color()); + if (d->scene) return to_qcolor(d->scene->background_color()); + if (!d->scene_builder) return {}; + return to_qcolor( + d->scene_builder->properties_value().background); } void Abs_Plot::set_background_color(const QColor& color) { - d->scene->set_background_color(from_qcolor(color)); + const Color value = from_qcolor(color); + if (d->scene) { + d->scene->set_background_color(value); + return; + } + if (!d->scene_builder) + ::renderive::error::unexpected( + "plot scene builder is unavailable"); + d->scene_builder + ->set<&Render_Scene_2D::Properties::background>(value); } void Abs_Plot::start_render_timer() { + if (!d->scene) return; d->scene->activate_view(); update_render_interval(); if (d->timer && !d->timer->isActive()) @@ -102,11 +148,11 @@ void Abs_Plot::start_render_timer() { void Abs_Plot::stop_render_timer() { if (d->timer) d->timer->stop(); - d->scene->deactivate_view(); + if (d->scene) d->scene->deactivate_view(); } void Abs_Plot::update_render_interval() { - if (!d->timer) + if (!d->timer || !d->scene) return; const double fps = std::max(1.0, d->scene->max_render_fps()); d->timer->setInterval(std::max(1, static_cast(std::lround(1000.0 / fps)))); @@ -119,6 +165,10 @@ void Abs_Plot::paintEvent(QPaintEvent*) { void Abs_Plot_Private::paint_event(Abs_Plot* plot) { QPainter painter(plot); painter.fillRect(plot->rect(), plot->background_color()); + if (!scene) { + painter.end(); + return; + } scene->with_frame([&painter](Image_View view) { if (!view.empty()) painter.drawImage(QPoint(0, 0), Qt_Image_Adapter::to_qimage(view)); @@ -128,6 +178,7 @@ void Abs_Plot_Private::paint_event(Abs_Plot* plot) { } void Abs_Plot_Private::draw_performance_overlay(Abs_Plot* plot, QPainter& painter) { + if (!scene) return; const auto overlay = scene->performance_overlay(); if (!overlay || !overlay->enabled()) return; @@ -177,6 +228,7 @@ void Abs_Plot_Private::draw_performance_overlay(Abs_Plot* plot, QPainter& painte bool Abs_Plot::event(QEvent* event) { const auto dispatch = [this, event](const Event& translated) { + if (!d->scene) return false; d->scene->dispatch_event(translated); if (!translated.is_accepted()) return false; @@ -186,17 +238,23 @@ bool Abs_Plot::event(QEvent* event) { switch (event->type()) { case QEvent::Resize: { auto* resize = static_cast(event); - d->scene->set_viewport_size(Qt_Event_Adapter::to_size(resize->size())); - d->scene->dispatch_event(Qt_Event_Adapter::to_resize_event(resize)); + const Size viewport = Qt_Event_Adapter::to_size(resize->size()); + if (d->scene) { + d->scene->set_viewport_size(viewport); + d->scene->dispatch_event(Qt_Event_Adapter::to_resize_event(resize)); + } else if (d->scene_builder) { + d->scene_builder + ->set<&Render_Scene_2D::Properties::viewport>(viewport); + } break; } case QEvent::Show: - d->scene->dispatch_event(Event(Event_Type::Show)); + if (d->scene) d->scene->dispatch_event(Event(Event_Type::Show)); if (d->continuous) start_render_timer(); break; case QEvent::Hide: - d->scene->dispatch_event(Event(Event_Type::Hide)); + if (d->scene) d->scene->dispatch_event(Event(Event_Type::Hide)); if (d->continuous) stop_render_timer(); break; diff --git a/Qt/plot/Plot.h b/Qt/plot/Plot.h index 3199634..16c3de6 100644 --- a/Qt/plot/Plot.h +++ b/Qt/plot/Plot.h @@ -15,6 +15,7 @@ public: [[nodiscard]] QColor background_color() const; [[nodiscard]] Render_Scene_2D* scene() const; [[nodiscard]] renderive_Owner root_renderable() const; + void add_renderable(renderive_Owner renderable); void set_background_color(const QColor& color); ~Abs_Plot() override; protected: diff --git a/Qt/plot/Plot_p.h b/Qt/plot/Plot_p.h index 92fba51..f4cac47 100644 --- a/Qt/plot/Plot_p.h +++ b/Qt/plot/Plot_p.h @@ -19,6 +19,7 @@ struct Abs_Plot_Private { void paint_event(Abs_Plot* plot); void draw_performance_overlay(Abs_Plot* plot, QPainter& painter); + std::unique_ptr scene_builder; std::unique_ptr scene; std::shared_ptr presentation_sink; QTimer* timer{}; diff --git a/Qt/tests/Qt_Bridge_Tests.cpp b/Qt/tests/Qt_Bridge_Tests.cpp index b6daaa4..f9f4796 100644 --- a/Qt/tests/Qt_Bridge_Tests.cpp +++ b/Qt/tests/Qt_Bridge_Tests.cpp @@ -11,7 +11,6 @@ TEST(Renderive_Qt, WidgetLifecycleDrivesAndStopsKernelScene) { ASSERT_NE(application, nullptr); Latency_Eager_Plot plot; plot.resize(240, 120); - plot.init(); const auto root = plot.root_renderable(); renderive_Owner x_axis; renderive_Owner y_axis; @@ -24,7 +23,9 @@ TEST(Renderive_Qt, WidgetLifecycleDrivesAndStopsKernelScene) { .set<&Frequency_Axis::Properties::coordinates>(Range{0.0, 10.0}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("X Axis") .build(); + plot.add_renderable(x_axis); y_axis = Axis::Builder{} .set<&Axis::Properties::orientation>(Orientation::Vertical) .set<&Axis::Properties::x>(20) @@ -33,13 +34,18 @@ TEST(Renderive_Qt, WidgetLifecycleDrivesAndStopsKernelScene) { .set<&Axis::Properties::coordinates>(Range{1.0, 0.0}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Y Axis") .build(); + plot.add_renderable(y_axis); spectrum = Spectrum::Builder(x_axis, y_axis) .set<&Spectrum::Properties::frequency_range>(Range{0.0, 10.0}) .set<&Spectrum::Properties::frequency_point_size>(8) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Spectrum") .build(); + plot.add_renderable(spectrum); + plot.init(); spectrum->update_samples(std::vector{0.1, 0.3, 0.8, 0.5, 0.9, 0.4, 0.2, 0.7}); plot.show(); QTimer::singleShot(150, application, &QCoreApplication::quit); diff --git a/render_2D/render_2D/renderable/Renderable.cpp b/render_2D/render_2D/renderable/Renderable.cpp index a7866af..24bad93 100644 --- a/render_2D/render_2D/renderable/Renderable.cpp +++ b/render_2D/render_2D/renderable/Renderable.cpp @@ -2,6 +2,7 @@ #include "Renderable_p.h" #include "../render/Blend2D_Cache.h" #include +#include namespace renderive { namespace detail { namespace { @@ -9,8 +10,12 @@ struct Renderable_Group final : Renderable { using Renderable::Renderable; }; } -renderive_Owner make_renderable_group(bool cache_enabled) { - return renderive_Owner::make(cache_enabled); +renderive_Owner make_renderable_group( + bool cache_enabled, std::string object_name) { + auto result = renderive_Owner::make(cache_enabled); + Renderable_Build_Access::set_object_name(*result, std::move(object_name)); + Renderable_Build_Access::set_topology(*result, {}); + return result; } } // namespace detail Renderable::Renderable(bool cache_enabled) : render_base(std::make_unique(), {.cache_enabled = cache_enabled}) {} diff --git a/render_2D/render_2D/renderable/Renderable.h b/render_2D/render_2D/renderable/Renderable.h index 74eb527..43d84bc 100644 --- a/render_2D/render_2D/renderable/Renderable.h +++ b/render_2D/render_2D/renderable/Renderable.h @@ -18,7 +18,8 @@ namespace detail { struct Painter; struct Renderable_Event_View; struct Renderable_State_Observer; -LIB_DECL::renderive_Owner make_renderable_group(bool cache_enabled); +LIB_DECL::renderive_Owner make_renderable_group( + bool cache_enabled, std::string object_name = {}); } enum struct Renderable_Observer_Event { None, diff --git a/render_2D/render_2D/renderable/Renderable_Builder.h b/render_2D/render_2D/renderable/Renderable_Builder.h index a51cba3..a3eaecd 100644 --- a/render_2D/render_2D/renderable/Renderable_Builder.h +++ b/render_2D/render_2D/renderable/Renderable_Builder.h @@ -2,7 +2,7 @@ #include "Renderable.h" #include #include -#include +#include #include #include #include @@ -17,92 +17,136 @@ class Abs_Axis; namespace detail { struct Paint_Overlay; } -template Validator_Type = No_Property_Validator> +template Validator_Type = + No_Property_Validator> struct Renderable_Builder { using Product = Product_Type; using Properties = Properties_Type; using Validator = Validator_Type; using Self = Renderable_Builder; - Renderable_Builder() requires std::default_initializable : constructor_(make_constructor()) {} - template requires (sizeof...(Args) > 0) && std::default_initializable + + Renderable_Builder() requires std::default_initializable + : constructor_(make_constructor()) {} + + template + requires (sizeof...(Args) > 0) && std::default_initializable explicit Renderable_Builder(Args&&... args) { (collect_constructor_rely(args), ...); constructor_ = make_constructor(std::forward(args)...); } - template Value> + + template Value> Self& set(Value&& value) { properties_.*Member = std::forward(value); return *this; } + template Configure> Self& configure(Configure&& configure) { std::invoke(std::forward(configure), properties_); return *this; } - Self& set_paint_rely(std::initializer_list> rely) { - paint_rely_.assign(rely); - return *this; - } - Self& set_render_rely(std::initializer_list> rely) { - render_rely_.assign(rely); - return *this; - } + Self& set_object_name(std::string name) { object_name_ = std::move(name); return *this; } + + Self& set_paint_rely( + std::initializer_list> rely) { + paint_rely_.assign(rely); + return *this; + } + + Self& set_render_rely( + std::initializer_list> rely) { + render_rely_.assign(rely); + return *this; + } + const Properties& properties_value() const noexcept { return properties_; } + [[nodiscard]] renderive_Owner build() { - if (!constructor_ || !valid_rely(paint_rely_) || !valid_rely(render_rely_) || !valid_rely(constructor_rely_)) return {}; - const auto* anchor = scene_anchor(); - if (!anchor) return {}; + if (!constructor_ || !valid_rely(paint_rely_) || + !valid_rely(render_rely_) || !valid_rely(constructor_rely_)) + return {}; + validator_(properties_); auto construct = std::exchange(constructor_, {}); auto result = construct(properties_); if (!result) return {}; - result->set_object_name(std::move(object_name_)); - auto attach = Scene_2D_Base::attach_builder(*anchor); - attach.attach(result); - for (const auto& parent : paint_rely_) attach.add_display_parent(result, parent); - for (const auto& parent : render_rely_) attach.add_dependency_parent(result, parent); + + Renderable_Build_Topology topology; + topology.display.reserve( + paint_rely_.size() + constructor_rely_.size()); + topology.dependency.reserve( + render_rely_.size() + constructor_rely_.size()); + + for (const auto& parent : paint_rely_) + topology.display.push_back( + detail::build_relationship(result, parent)); + for (const auto& parent : render_rely_) + topology.dependency.push_back( + detail::build_relationship(result, parent)); for (const auto& parent : constructor_rely_) { - attach.add_dependency_parent(result, parent); - if constexpr (std::derived_from) attach.add_display_parent(result, parent); - else attach.add_display_parent(parent, result); + topology.dependency.push_back( + detail::build_relationship(result, parent)); + if constexpr (std::derived_from) + topology.display.push_back( + detail::build_relationship(result, parent)); + else + topology.display.push_back( + detail::build_relationship(parent, result)); } + + detail::Renderable_Build_Access::set_object_name( + *result, std::move(object_name_)); + detail::Renderable_Build_Access::set_topology( + *result, std::move(topology)); return result; } + private: - using Constructor = std::function(const Properties&)>; + using Constructor = + std::function(const Properties&)>; + template static Constructor make_constructor(Args&&... args) { - auto stored = std::make_shared...>>(std::forward(args)...); - return [stored = std::move(stored)](const Properties& properties) mutable { + auto stored = + std::make_shared...>>( + std::forward(args)...); + return [stored = std::move(stored)]( + const Properties& properties) mutable { return std::apply( [&](auto&... values) { - return renderive_Owner(renderable_inheritance::Builder_Access::make(properties, std::move(values)...)); + return renderive_Owner( + renderable_inheritance::Builder_Access::make( + properties, std::move(values)...)); }, *stored); }; } - template requires std::derived_from + + template + requires std::derived_from void collect_constructor_rely(const renderive_Owner& value) { constructor_rely_.emplace_back(value); } + template void collect_constructor_rely(const T&) {} - static bool valid_rely(const std::vector>& rely) { - for (const auto& value : rely) if (!value) return false; + + static bool valid_rely( + const std::vector>& rely) { + for (const auto& value : rely) + if (!value) return false; return true; } - [[nodiscard]] const renderive_Owner* scene_anchor() const noexcept { - if (!paint_rely_.empty()) return &paint_rely_.front(); - if (!render_rely_.empty()) return &render_rely_.front(); - if (!constructor_rely_.empty()) return &constructor_rely_.front(); - return nullptr; - } + Properties properties_{}; [[no_unique_address]] Validator validator_{}; Constructor constructor_; @@ -111,4 +155,4 @@ private: std::vector> render_rely_; std::vector> constructor_rely_; }; -} +} // namespace renderive diff --git a/render_2D/render_2D/scene/Render_Scene_2D.cpp b/render_2D/render_2D/scene/Render_Scene_2D.cpp index 9013849..ef15936 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.cpp @@ -109,12 +109,6 @@ detail::Render_Scene_2D::Render_Scene_2D( auto& impl = d_func(); impl.frame_control = std::make_unique( std::move(frame_strategy)); - auto root = detail::make_renderable_group(true); - root->set_object_name("root"); - auto builder = attach_builder(); - if (builder.attach(root) != Scene_Edit_Error::none) - ::renderive::error::unexpected( - "validated plot root attachment failed"); } detail::Render_Scene_2D::~Render_Scene_2D() { d_func().shutdown(); diff --git a/render_2D/render_2D/scene/Render_Scene_2D.h b/render_2D/render_2D/scene/Render_Scene_2D.h index b96cb70..f245be3 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.h +++ b/render_2D/render_2D/scene/Render_Scene_2D.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace renderive { struct Performance_Overlay; struct Scene2D_Frame_Data : Abstract_Frame {}; @@ -32,12 +33,39 @@ struct Presentation_Sink { virtual void request_present(Rect dirty_rect) = 0; }; namespace detail { +template +struct Scene_2D_Builder +: ::renderive::scene::Scene_Builder { + using Base = ::renderive::scene::Scene_Builder; + + Scene_2D_Builder() : Base() { add_root(); } + + template + requires (sizeof...(Args) > 0) + explicit Scene_2D_Builder(Args&&... args) + : Base(std::forward(args)...) { + add_root(); + } + + [[nodiscard]] renderive_Owner<::renderive::Renderable> + root_renderable() const noexcept { + return root_; + } + +private: + void add_root() { + root_ = make_renderable_group(true, "root"); + Base::add_renderable(root_); + } + + renderive_Owner<::renderive::Renderable> root_; +}; + template struct Scene : ::renderive::scene::scene_base { using Base = ::renderive::scene::scene_base; template - using Business_Builder = - ::renderive::scene::Scene_Builder; + using Business_Builder = Scene_2D_Builder; protected: struct Builder : Base::template next_Builder {}; struct State : Base::template next_State {}; diff --git a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp index 75eafd9..f2ae3ba 100644 --- a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp +++ b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp @@ -1,4 +1,5 @@ #include "render_2D/scene/Render_Scene_2D.h" +#include #include #include @@ -13,7 +14,7 @@ std::unique_ptr make_scene( .configure([&](Render_Scene_2D::Properties& properties) { properties = state; }) - .build(); + .build().value(); } TEST(RenderScene2DFramePipeline, BuilderOwnsConstructionAndState) { @@ -25,6 +26,72 @@ TEST(RenderScene2DFramePipeline, BuilderOwnsConstructionAndState) { EXPECT_EQ(scene->root_renderable()->object_name(), "root"); } + +TEST(RenderScene2DFramePipeline, SceneBuilderOwnsRenderableGroupAndObjectNames) { + auto builder = Render_Scene_2D::Builder( + std::make_shared()); + const auto root = builder.root_renderable(); + auto child = detail::make_renderable_group(false, "child"); + Renderable_Build_Topology topology; + topology.dependency.push_back( + detail::build_relationship(child, root)); + topology.display.push_back( + detail::build_relationship(child, root)); + detail::Renderable_Build_Access::set_topology(*child, std::move(topology)); + builder.add_renderable(child); + + auto built_scene = builder.build(); + ASSERT_TRUE(built_scene); + ASSERT_TRUE(*built_scene); + EXPECT_EQ(child->object_name(), "child"); + EXPECT_EQ((*built_scene)->observer().renderable_count, 2U); +} + +TEST(RenderScene2DFramePipeline, SceneBuilderRejectsMissingDependencyEndpoint) { + auto builder = Render_Scene_2D::Builder( + std::make_shared()); + const auto root = builder.root_renderable(); + auto external = detail::make_renderable_group(false, "external"); + auto child = detail::make_renderable_group(false, "child"); + Renderable_Build_Topology topology; + topology.dependency.push_back( + detail::build_relationship(child, external)); + topology.display.push_back( + detail::build_relationship(child, root)); + detail::Renderable_Build_Access::set_topology(*child, std::move(topology)); + builder.add_renderable(child); + + auto built_scene = builder.build(); + ASSERT_FALSE(built_scene); + EXPECT_EQ(built_scene.error(), + Scene_Build_Result::dependency_endpoint_missing); +} + +TEST(RenderScene2DFramePipeline, SceneBuilderRejectsDependencyCycle) { + auto builder = Render_Scene_2D::Builder( + std::make_shared()); + auto first = detail::make_renderable_group(false, "first"); + auto second = detail::make_renderable_group(false, "second"); + + Renderable_Build_Topology first_topology; + first_topology.dependency.push_back( + detail::build_relationship(first, second)); + detail::Renderable_Build_Access::set_topology( + *first, std::move(first_topology)); + + Renderable_Build_Topology second_topology; + second_topology.dependency.push_back( + detail::build_relationship(second, first)); + detail::Renderable_Build_Access::set_topology( + *second, std::move(second_topology)); + + builder.add_renderable(first); + builder.add_renderable(second); + auto built_scene = builder.build(); + ASSERT_FALSE(built_scene); + EXPECT_EQ(built_scene.error(), Scene_Build_Result::dependency_cycle); +} + TEST(RenderScene2DFramePipeline, SceneValidityDoesNotGateStrategyProgress) { Render_Scene_2D::State state; state.viewport = {80, 45}; diff --git a/render_2D/tests/render_2D_Integration_Tests.cpp b/render_2D/tests/render_2D_Integration_Tests.cpp index c4a0bf0..55c8268 100644 --- a/render_2D/tests/render_2D_Integration_Tests.cpp +++ b/render_2D/tests/render_2D_Integration_Tests.cpp @@ -13,7 +13,7 @@ TEST(RenderScene2DIntegration, BuilderStatePublishesAtFrameBoundary) { .configure([&](Render_Scene_2D::Properties& properties) { properties = initial; }) - .build(); + .build().value(); scene->activate_view(); ASSERT_EQ(scene->render_frame().value(), Plot_Render_Status::rendered); ASSERT_EQ(scene->wait_for_render(), Scene_Render_Result::none); @@ -28,7 +28,7 @@ TEST(RenderScene2DIntegration, FrameImageMatchesConfiguredViewport) { .configure([&](Render_Scene_2D::Properties& properties) { properties = state; }) - .build(); + .build().value(); scene->activate_view(); ASSERT_EQ(scene->render_frame().value(), Plot_Render_Status::rendered); ASSERT_EQ(scene->wait_for_render(), Scene_Render_Result::none); @@ -47,7 +47,7 @@ TEST(RenderScene2DIntegration, FrameStrategyIsReplacedInsideRenderableEdit) { .configure([&](Render_Scene_2D::Properties& properties) { properties = state; }) - .build(); + .build().value(); const auto operation = scene->edit_renderables( [](Scene_2D_Base::Renderable_Editor& editor) { editor.replace_frame_control_strategy( diff --git a/render_3D/render_3D/Render_Scene_3D.cpp b/render_3D/render_3D/Render_Scene_3D.cpp index 5492139..4f1d8f3 100644 --- a/render_3D/render_3D/Render_Scene_3D.cpp +++ b/render_3D/render_3D/Render_Scene_3D.cpp @@ -429,14 +429,6 @@ detail::Render_Scene_3D::Render_Scene_3D( std::move(frame_strategy)); impl.visual = std::move(visual); impl.point_id = impl.visual->renderable_id(); - const auto attachment = edit_renderables( - [&impl](Scene_3D_Base::Renderable_Editor& editor) { - static_cast(editor.attach( - renderive_Owner(impl.visual))); - }); - if (attachment.wait() != Scene_Edit_Error::none) - ::renderive::error::unexpected( - "validated point visual attachment failed"); const State initial = impl.scene_state.read( [](const State& value) { return value; diff --git a/render_3D/render_3D/renderable/Renderable_Builder.h b/render_3D/render_3D/renderable/Renderable_Builder.h index a3e1b82..3c21cf0 100644 --- a/render_3D/render_3D/renderable/Renderable_Builder.h +++ b/render_3D/render_3D/renderable/Renderable_Builder.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,52 +11,79 @@ #include #include namespace renderive::render_3d { -template Validator_Type = No_Property_Validator> +template Validator_Type = + No_Property_Validator> struct Renderable_Builder { using Product = Product_Type; using Properties = Properties_Type; using Validator = Validator_Type; using Self = Renderable_Builder; - Renderable_Builder() requires std::default_initializable : constructor_(make_constructor()) {} - template requires (sizeof...(Args) > 0) && std::default_initializable - explicit Renderable_Builder(Args&&... args) : constructor_(make_constructor(std::forward(args)...)) {} - template Value> + + Renderable_Builder() requires std::default_initializable + : constructor_(make_constructor()) {} + + template + requires (sizeof...(Args) > 0) && std::default_initializable + explicit Renderable_Builder(Args&&... args) + : constructor_(make_constructor(std::forward(args)...)) {} + + template Value> Self& set(Value&& value) { properties_.*Member = std::forward(value); return *this; } + template Configure> Self& configure(Configure&& configure) { std::invoke(std::forward(configure), properties_); return *this; } + Self& set_object_name(std::string name) { object_name_ = std::move(name); return *this; } + [[nodiscard]] std::shared_ptr build() { validator_(properties_); auto construct = std::exchange(constructor_, {}); auto result = construct(properties_); - result->set_object_name(std::move(object_name_)); - return ::renderive::inheritance::initialize(std::move(result), [](auto&) {}); + result = ::renderive::inheritance::initialize( + std::move(result), [](auto&) {}); + if (result) { + ::renderive::detail::Renderable_Build_Access::set_object_name( + *result, std::move(object_name_)); + ::renderive::detail::Renderable_Build_Access::set_topology( + *result, {}); + } + return result; } + private: - using Constructor = std::function(const Properties&)>; + using Constructor = + std::function(const Properties&)>; + template static Constructor make_constructor(Args&&... args) { - auto stored = std::make_shared...>>(std::forward(args)...); - return [stored = std::move(stored)](const Properties& properties) mutable { + auto stored = + std::make_shared...>>( + std::forward(args)...); + return [stored = std::move(stored)]( + const Properties& properties) mutable { return std::apply( [&](auto&... values) { - return ::renderive::renderable_inheritance::Builder_Access::make(properties, std::move(values)...); + return ::renderive::renderable_inheritance::Builder_Access::make< + Product>(properties, std::move(values)...); }, *stored); }; } + Properties properties_{}; [[no_unique_address]] Validator validator_{}; Constructor constructor_; std::string object_name_; }; -} +} // namespace renderive::render_3d diff --git a/render_3D/tests/Point_Render_Integration_Tests.cpp b/render_3D/tests/Point_Render_Integration_Tests.cpp index 052f7d6..19c7f9a 100644 --- a/render_3D/tests/Point_Render_Integration_Tests.cpp +++ b/render_3D/tests/Point_Render_Integration_Tests.cpp @@ -14,7 +14,7 @@ std::unique_ptr make_scene( .configure([&](Render_Scene_3D::Properties& properties) { properties = state; }) - .build(); + .build().value(); } TEST(PointRenderIntegration, BuilderConstructsSceneFromStateAndStrategy) { diff --git a/web_server/app/Web_Plot_Session.cpp b/web_server/app/Web_Plot_Session.cpp index 261b04e..2a871dd 100644 --- a/web_server/app/Web_Plot_Session.cpp +++ b/web_server/app/Web_Plot_Session.cpp @@ -1,7 +1,10 @@ #include "Web_Plot_Session.h" #include "Pixel_Frame.h" #include "render_2D/scene/Render_Scene_2D.h" +#include + #include +#include #include namespace renderive::web { @@ -12,10 +15,14 @@ struct Web_Plot_Session::Impl { Impl() { rebuild(); } void rebuild() { - scene = Render_Scene_2D::Builder( + auto built_scene = Render_Scene_2D::Builder( std::make_shared()) .set<&Render_Scene_2D::Properties::viewport>(viewport) .build(); + if (!built_scene) + ::renderive::error::unexpected( + "web plot scene topology validation failed"); + scene = std::move(*built_scene); scene->activate_view(); } std::optional handle(const Web_Event& event) { diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index c937c74..160c7ec 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -15,6 +15,8 @@ #include "render_2D/plottable/Waterfall.h" #include "render_2D/scene/Render_Scene_2D.h" +#include + #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include @@ -225,11 +228,11 @@ public: private: void rebuild(Gallery_Frame_Mode mode) { - scene_ = Render_Scene_2D::Builder(make_strategy(mode)) + auto scene_builder = Render_Scene_2D::Builder(make_strategy(mode)); + scene_builder .set<&Render_Scene_2D::Properties::viewport>(viewport_) - .set<&Render_Scene_2D::Properties::background>(Color{3, 8, 14, 255}) - .build(); - const auto root = scene_->root_renderable(); + .set<&Render_Scene_2D::Properties::background>(Color{3, 8, 14, 255}); + const auto root = scene_builder.root_renderable(); const bool numeric_domain = case_id_ == "sweep_spectrum" || case_id_ == "constellation"; const bool time_domain = case_id_ == "frequency_trace"; @@ -244,7 +247,6 @@ private: ? Orientation::Horizontal : Orientation::Vertical; time_axis_ = Time_Axis::Builder{} - .set_object_name("Time Axis") .set<&Time_Axis::Properties::orientation>(orientation) .set<&Time_Axis::Properties::visible_count>(80) .set<&Time_Axis::Properties::x>(left) @@ -257,11 +259,12 @@ private: .set<&Time_Axis::Properties::color>(Color{69, 221, 190, 255}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Time Axis") .build(); + scene_builder.add_renderable(time_axis_); } if (!time_domain && !numeric_domain) { frequency_axis_ = Frequency_Axis::Builder{} - .set_object_name("Frequency Axis") .set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal) .set<&Frequency_Axis::Properties::coordinates>(Range{88'000'000.0, 108'000'000.0}) .set<&Frequency_Axis::Properties::x>(left) @@ -272,10 +275,11 @@ private: .set<&Frequency_Axis::Properties::drag>(true) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Frequency Axis") .build(); + scene_builder.add_renderable(frequency_axis_); } else if (numeric_domain) { domain_axis_ = Axis::Builder{} - .set_object_name("Domain Axis") .set<&Axis::Properties::orientation>(Orientation::Horizontal) .set<&Axis::Properties::coordinates>( case_id_ == "constellation" ? Range{-1.25, 1.25} : Range{0.0, 300.0}) @@ -287,11 +291,12 @@ private: .set<&Axis::Properties::drag>(true) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Domain Axis") .build(); + scene_builder.add_renderable(domain_axis_); } if (case_id_ != "waterfall") { value_axis_ = Axis::Builder{} - .set_object_name("Value Axis") .set<&Axis::Properties::orientation>(Orientation::Vertical) .set<&Axis::Properties::coordinates>( case_id_ == "constellation" @@ -303,11 +308,12 @@ private: .set<&Axis::Properties::color>(Color{118, 145, 184, 255}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Value Axis") .build(); + scene_builder.add_renderable(value_axis_); } if (case_id_ == "spectrum" || case_id_ == "selection_overlay") { spectrum_ = Spectrum::Builder(frequency_axis_, value_axis_) - .set_object_name("Spectrum") .set<&Spectrum::Properties::frequency_range>(Range{88'000'000.0, 108'000'000.0}) .set<&Spectrum::Properties::frequency_point_size>(512) .set<&Spectrum::Properties::center_frequency>(98'000'000.0) @@ -317,28 +323,31 @@ private: .set<&Spectrum::Properties::min_pen>(Pen{Color{122, 162, 255, 255}, 1.2}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Spectrum") .build(); + scene_builder.add_renderable(spectrum_); if (case_id_ == "selection_overlay") { selection_ = Selection_Rectangle_Overlay::Builder(frequency_axis_, value_axis_) - .set_object_name("Selection Overlay") .set<&Selection_Rectangle_Overlay::Properties::selection_brush>( Brush{Color{23, 59, 102, 90}, Brush_Style::Solid}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Selection Overlay") .build(); + scene_builder.add_renderable(selection_); } } else if (case_id_ == "waterfall") { waterfall_ = Waterfall::Builder(frequency_axis_, time_axis_) - .set_object_name("Waterfall") .set<&Waterfall::Properties::frequency_range>(Range{88'000'000.0, 108'000'000.0}) .set<&Waterfall::Properties::power_range>(Range{-120.0, -20.0}) .set<&Waterfall::Properties::frequency_bin_count>(256) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Waterfall") .build(); + scene_builder.add_renderable(waterfall_); } else if (case_id_ == "afterglow") { afterglow_ = Afterglow::Builder(frequency_axis_, value_axis_) - .set_object_name("Afterglow") .set<&Afterglow::Properties::frequency_range>(Range{88'000'000.0, 108'000'000.0}) .set<&Afterglow::Properties::power_range>(Range{-120.0, -20.0}) .set<&Afterglow::Properties::frequency_point_size>(192) @@ -346,34 +355,44 @@ private: .set<&Afterglow::Properties::attenuation_rate>(0.18) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Afterglow") .build(); + scene_builder.add_renderable(afterglow_); } else if (case_id_ == "sweep_spectrum") { sweep_ = Sweep_Spectrum::Builder(domain_axis_, value_axis_) - .set_object_name("Sweep Spectrum") .set<&Sweep_Spectrum::Properties::frequency_range>(Range{0.0, 300.0}) .set<&Sweep_Spectrum::Properties::bins_per_block>(static_cast(sweep_bins_per_block)) .set<&Sweep_Spectrum::Properties::block_count>(static_cast(sweep_block_count)) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Sweep Spectrum") .build(); + scene_builder.add_renderable(sweep_); } else if (case_id_ == "frequency_trace") { trace_ = Frequency_Trace::Builder(time_axis_, value_axis_) - .set_object_name("Frequency Trace") .set<&Frequency_Trace::Properties::pen>(Pen{Color{53, 230, 178, 255}, 2.0}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Frequency Trace") .build(); + scene_builder.add_renderable(trace_); } else if (case_id_ == "constellation") { constellation_ = Constellation_Diagram::Builder(domain_axis_, value_axis_) - .set_object_name("Constellation Diagram") .set<&Constellation_Diagram::Properties::i_range>(Range{-1.25, 1.25}) .set<&Constellation_Diagram::Properties::q_range>(Range{-1.25, 1.25}) .set<&Constellation_Diagram::Properties::point_color>(Color{73, 230, 195, 255}) .set<&Constellation_Diagram::Properties::anchor_color>(Color{255, 209, 102, 255}) .set_paint_rely({root}) .set_render_rely({root}) + .set_object_name("Constellation Diagram") .build(); + scene_builder.add_renderable(constellation_); } + auto built_scene = scene_builder.build(); + if (!built_scene) + ::renderive::error::unexpected( + "gallery 2D scene topology validation failed"); + scene_ = std::move(*built_scene); scene_->activate_view(); } void update_samples() { diff --git a/web_server/app/render_3D/Gallery_Scene3D.cpp b/web_server/app/render_3D/Gallery_Scene3D.cpp index 1ecb3f0..2f76af2 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.cpp +++ b/web_server/app/render_3D/Gallery_Scene3D.cpp @@ -5,10 +5,13 @@ #include "../common/Pixel_Frame.h" #include "render_3D/Render_Scene_3D.h" +#include + #include #include #include #include +#include #include #include @@ -211,10 +214,14 @@ private: visual_ = Point_Visual::Builder(std::move(points)) .set_object_name(case_id_ + " Points") .build(); - scene_ = Render_Scene_3D::Builder(visual_, make_strategy(mode)) + auto built_scene = Render_Scene_3D::Builder(visual_, make_strategy(mode)) .set<&Render_Scene_3D::Properties::viewport>(extent_) .set<&Render_Scene_3D::Properties::visual_family>(visual_family(case_id_)) .build(); + if (!built_scene) + ::renderive::error::unexpected( + "gallery 3D scene topology validation failed"); + scene_ = std::move(*built_scene); } std::string case_id_;