From 389ff81d42274388f35ca10e0111269b0df48b39 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Sun, 16 Aug 2026 18:33:24 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Kernel/CMakeLists.txt | 4 +- .../renderive/base/memory/Memory_Resource.hpp | 4 +- .../src/renderive/base/node/Multiway_Node.hpp | 5 +- .../renderive/base/property/Validators.hpp | 3 +- Kernel/src/renderive/capture/Capture.cpp | 17 +-- Kernel/src/renderive/error/Error_Policy.cpp | 65 ++++++++++ Kernel/src/renderive/error/Error_Policy.hpp | 45 +++++++ .../strategy/flow/Flow_Refresh_Strategy.inl | 3 +- .../low_latency/Low_Latency_Strategy.inl | 3 +- .../real_time_data/Attach_Real_Time_Data.hpp | 3 +- .../base/Real_Time_Data_Base.cpp | 6 +- .../render_graph/External_Operation.cpp | 36 +++--- .../renderive/render_graph/Frame_Analysis.cpp | 9 +- .../renderive/render_graph/Render_Plan.cpp | 19 +-- .../detail/Render_Graph_Runtime.cpp | 46 +++++-- .../renderable/Renderable_Graph_Builder.cpp | 11 +- .../renderable/Renderive_Id_Allocator.cpp | 3 +- .../renderable/base/Renderable_Base.cpp | 13 +- .../scene/base/Frame_Render_Snapshot.hpp | 15 +-- .../src/renderive/scene/base/Scene_Base.cpp | 115 +++++++++++------- .../scene/dependency/Dependency_Resolver.hpp | 9 +- .../renderive/error/Error_Policy_Test.cpp | 35 ++++++ Qt/CMakeLists.txt | 4 +- render_2D/CMakeLists.txt | 4 +- render_2D/render_2D/axis/Numeric_Axis.h | 3 +- render_2D/render_2D/scene/Plot_Scene.cpp | 3 +- .../scene/detail/Plot_Scene_Model.hpp | 8 +- render_3D/CMakeLists.txt | 4 +- render_3D/render_3D/Point_Scene.cpp | 49 +++++--- render_3D/render_3D/Point_Visual.cpp | 11 +- .../detail/Datoviz_Visual_Backend.cpp | 113 +++++++++-------- .../render_3D/detail/Datoviz_Visual_Backend.h | 4 +- .../detail/Gpu_Completion_Service.cpp | 14 ++- render_3D/render_3D/detail/Render_Domain.cpp | 15 ++- render_3D/render_3D/detail/Render_Domain.h | 20 ++- render_3D/render_3D/visuals/Basic_Visual.h | 11 +- web_server/CMakeLists.txt | 4 +- web_server/app/Gallery_Observer_Adminive.h | 3 +- web_server/app/Gallery_Plot_Session.cpp | 10 +- web_server/app/Gallery_Protocol.cpp | 7 +- web_server/app/Gallery_Renderables.h | 3 +- web_server/app/render_2D/Gallery_Scene2D.cpp | 15 +-- web_server/app/render_3D/Gallery_Scene3D.cpp | 17 +-- web_server/tests/Web_Bridge_Tests.cpp | 6 +- 44 files changed, 536 insertions(+), 261 deletions(-) create mode 100644 Kernel/src/renderive/error/Error_Policy.cpp create mode 100644 Kernel/src/renderive/error/Error_Policy.hpp create mode 100644 Kernel/tests/renderive/error/Error_Policy_Test.cpp diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 065a873..507e824 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -70,7 +70,9 @@ if (RENDERIVE_BUILD_TESTS) target_link_libraries("${Renderive_Kernel_test_target}" PRIVATE Renderive_Kernel GTest::gtest_main) renderive_stage_kernel_runtime("${Renderive_Kernel_test_target}") add_test(NAME "${Renderive_Kernel_test_target}" COMMAND "${Renderive_Kernel_test_target}") - set_tests_properties("${Renderive_Kernel_test_target}" PROPERTIES LABELS "Renderive_Kernel") + set_tests_properties("${Renderive_Kernel_test_target}" PROPERTIES + LABELS "Renderive_Kernel" + ENVIRONMENT "RENDERIVE_ERROR_MODE=exception") list(APPEND Renderive_Kernel_test_targets "${Renderive_Kernel_test_target}") endforeach () add_custom_target(Renderive_Kernel_check diff --git a/Kernel/src/renderive/base/memory/Memory_Resource.hpp b/Kernel/src/renderive/base/memory/Memory_Resource.hpp index 0bf47c1..b47cdf4 100644 --- a/Kernel/src/renderive/base/memory/Memory_Resource.hpp +++ b/Kernel/src/renderive/base/memory/Memory_Resource.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -61,6 +62,7 @@ Memory_Resource_Unique_Ptr make_memory_resource_unique(std::pmr::memory_r return Memory_Resource_Unique_Ptr(value, Memory_Resource_Deleter{&resource}); } catch (...) { resource.deallocate(storage, sizeof(Value), alignof(Value)); - throw; + ::renderive::error::unexpected( + "allocating from memory resource", std::current_exception()); } } diff --git a/Kernel/src/renderive/base/node/Multiway_Node.hpp b/Kernel/src/renderive/base/node/Multiway_Node.hpp index 7058712..6549cb9 100644 --- a/Kernel/src/renderive/base/node/Multiway_Node.hpp +++ b/Kernel/src/renderive/base/node/Multiway_Node.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -24,10 +25,10 @@ public: } void insert_child(std::size_t index, Multiway_Node& child) { if (index > children_.size()) { - throw std::out_of_range("multiway node child index"); + ::renderive::error::unexpected("multiway node child index"); } if (&child == this || child.is_ancestor_of(*this)) { - throw std::invalid_argument("multiway node cycle"); + ::renderive::error::unexpected("multiway node cycle"); } if (child.parent_ == this) { const auto current = static_cast(std::find(children_.begin(), children_.end(), &child) - children_.begin()); diff --git a/Kernel/src/renderive/base/property/Validators.hpp b/Kernel/src/renderive/base/property/Validators.hpp index 8b0d9f2..11d4b43 100644 --- a/Kernel/src/renderive/base/property/Validators.hpp +++ b/Kernel/src/renderive/base/property/Validators.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "Concepts.hpp" #include template @@ -10,7 +11,7 @@ struct Range_Validator { static_assert(!(Max < Min)); void operator()(const Value& value) const { if (value < Min || Max < value) { - throw std::out_of_range("property value is outside the declared range"); + ::renderive::error::unexpected("property value is outside the declared range"); } } }; diff --git a/Kernel/src/renderive/capture/Capture.cpp b/Kernel/src/renderive/capture/Capture.cpp index 1fa77d2..ae4cfe1 100644 --- a/Kernel/src/renderive/capture/Capture.cpp +++ b/Kernel/src/renderive/capture/Capture.cpp @@ -1,3 +1,4 @@ +#include #include "Capture.hpp" #include #include @@ -14,7 +15,7 @@ Capture_Request_Result Capture_Controller::capture_frames(std::size_t count) { Capture_Session_Id id = next_session_id_.load(std::memory_order_relaxed); for (;;) { if (id == 0 || id == std::numeric_limits::max()) - throw std::overflow_error("capture session id exhausted"); + ::renderive::error::unexpected("capture session id exhausted"); if (next_session_id_.compare_exchange_weak( id, id + 1, std::memory_order_relaxed, std::memory_order_relaxed)) @@ -86,17 +87,17 @@ Capture_Controller_State Capture_Controller::state() const noexcept { void Capture_Repository::begin_session(Capture_Session_Id session_id, std::size_t requested_count) { if (session_id == 0 || requested_count == 0) - throw std::logic_error("invalid capture session invariant"); + ::renderive::error::unexpected("invalid capture session invariant"); std::lock_guard lock(mutex_); if (std::any_of(sessions_.begin(), sessions_.end(), [session_id](const auto& session) { return session.session_id == session_id; })) - throw std::logic_error("capture session already exists"); + ::renderive::error::unexpected("capture session already exists"); while (sessions_.size() >= retained_session_count) { const auto completed = std::find_if(sessions_.begin(), sessions_.end(), [](const Capture_Session& session) { return !session.active(); }); if (completed == sessions_.end()) - throw std::logic_error("capture repository retention is occupied by active sessions"); + ::renderive::error::unexpected("capture repository retention is occupied by active sessions"); sessions_.erase(completed); } sessions_.push_back({session_id, requested_count, {}}); @@ -108,18 +109,18 @@ void Capture_Repository::publish(Capture_Session_Id session_id, std::shared_ptr render_plan, Frame_Analysis analysis) { if (!snapshot || !render_plan) - throw std::logic_error("captured frame snapshot or render plan is null"); + ::renderive::error::unexpected("captured frame snapshot or render plan is null"); if (analysis.frame_id != snapshot->frame_id || analysis.render_plan_version != snapshot->render_plan_version || render_plan->version != snapshot->render_plan_version) - throw std::logic_error("captured frame analysis does not match snapshot or render plan"); + ::renderive::error::unexpected("captured frame analysis does not match snapshot or render plan"); std::lock_guard lock(mutex_); const auto iterator = std::find_if(sessions_.begin(), sessions_.end(), [session_id](const auto& session) { return session.session_id == session_id; }); if (iterator == sessions_.end()) - throw std::logic_error("capture session does not exist"); + ::renderive::error::unexpected("capture session does not exist"); if (!iterator->active()) - throw std::logic_error("capture session is complete"); + ::renderive::error::unexpected("capture session is complete"); iterator->frames.push_back({std::move(snapshot), std::move(render_plan), std::move(analysis)}); } diff --git a/Kernel/src/renderive/error/Error_Policy.cpp b/Kernel/src/renderive/error/Error_Policy.cpp new file mode 100644 index 0000000..4a98406 --- /dev/null +++ b/Kernel/src/renderive/error/Error_Policy.cpp @@ -0,0 +1,65 @@ +#include "Error_Policy.hpp" +#include +#include +#include +#include +namespace renderive::error { +namespace { +Mode read_mode() noexcept { + const char* value = std::getenv("RENDERIVE_ERROR_MODE"); + if (value && std::strcmp(value, "exception") == 0) + return Mode::exception; + return Mode::fast_fail; +} +std::atomic mode_value{read_mode()}; +void print_exception(std::exception_ptr exception) noexcept { + if (!exception) + return; + try { + std::rethrow_exception(exception); + } catch (const std::exception& value) { + std::fprintf(stderr, ": %s", value.what()); + } catch (...) { + std::fprintf(stderr, ": unknown exception"); + } +} +} +Mode mode() noexcept { + return mode_value.load(std::memory_order_relaxed); +} +void set_mode(Mode value) noexcept { + mode_value.store(value, std::memory_order_relaxed); +} +[[noreturn]] void fast_fail(std::string_view message) noexcept { + std::fprintf(stderr, "Renderive unexpected error: %.*s\n", static_cast(message.size()), message.data()); + std::fflush(stderr); + std::abort(); +} +[[noreturn]] void fast_fail(std::string_view context, std::exception_ptr exception) noexcept { + std::fprintf(stderr, "Renderive unexpected error"); + if (!context.empty()) + std::fprintf(stderr, " during %.*s", static_cast(context.size()), context.data()); + print_exception(exception); + std::fprintf(stderr, "\n"); + std::fflush(stderr); + std::abort(); +} +std::exception_ptr capture( + std::string_view context, std::exception_ptr exception) { + if (mode() == Mode::exception) { + if (exception) + return exception; + return std::make_exception_ptr(std::runtime_error( + context.empty() ? "Renderive unexpected error" : std::string(context))); + } + fast_fail(context, exception); +} +[[noreturn]] void unexpected(std::string_view context, std::exception_ptr exception) { + if (mode() == Mode::exception) { + if (exception) + std::rethrow_exception(exception); + throw std::runtime_error(context.empty() ? "Renderive unexpected error" : std::string(context)); + } + fast_fail(context, exception); +} +} diff --git a/Kernel/src/renderive/error/Error_Policy.hpp b/Kernel/src/renderive/error/Error_Policy.hpp new file mode 100644 index 0000000..025ba46 --- /dev/null +++ b/Kernel/src/renderive/error/Error_Policy.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include +#include +#include +#include +namespace renderive::error { +enum class Mode { + fast_fail, + exception +}; +[[nodiscard]] Mode mode() noexcept; +void set_mode(Mode mode) noexcept; +[[noreturn]] void fast_fail(std::string_view message) noexcept; +[[noreturn]] void fast_fail(std::string_view context, std::exception_ptr exception) noexcept; +[[nodiscard]] std::exception_ptr capture( + std::string_view context, std::exception_ptr exception); +[[noreturn]] void unexpected(std::string_view context, std::exception_ptr exception); +template + requires std::constructible_from +[[nodiscard]] std::exception_ptr capture(Arguments&&... arguments) { + if (mode() == Mode::exception) + return std::make_exception_ptr( + Exception(std::forward(arguments)...)); + try { + Exception exception(std::forward(arguments)...); + fast_fail(exception.what()); + } catch (...) { + fast_fail("Renderive unexpected error"); + } +} +template + requires std::constructible_from +[[noreturn]] void unexpected(Arguments&&... arguments) { + if (mode() == Mode::exception) + throw Exception(std::forward(arguments)...); + try { + Exception exception(std::forward(arguments)...); + fast_fail(exception.what()); + } catch (...) { + fast_fail("Renderive unexpected error"); + } +} +} diff --git a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl index c821a0a..c8920d1 100644 --- a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl +++ b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.inl @@ -1,4 +1,5 @@ #pragma once +#include template Flow_Refresh_Strategy::Painter_Lease::Painter_Lease(Flow_Refresh_Strategy& strategy) : strategy_(&strategy) { @@ -147,7 +148,7 @@ template Flow_Refresh_Strategy::Flow_Refresh_Strategy(std::pmr::memory_resource& memory_resource, Observer observer, std::size_t capacity) : Frame_Control_Strategy_Base(), memory_resource_(&memory_resource), observer_(std::move(observer)) { if (capacity == 0) - throw std::invalid_argument("flow refresh queue capacity must be greater than zero"); + ::renderive::error::unexpected("flow refresh queue capacity must be greater than zero"); frames_.set_capacity(static_cast(capacity)); } template diff --git a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl index f785ae1..eae9b53 100644 --- a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl +++ b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -358,7 +359,7 @@ bool Low_Latency_Strategy::discard_stale_latest_da template double Low_Latency_Strategy::checked_configuration_frequency(double frequency_hz) { if (!std::isfinite(frequency_hz) || frequency_hz <= 0.0) - throw std::invalid_argument("low-latency frame-control configuration frequency must be positive and finite"); + ::renderive::error::unexpected("low-latency frame-control configuration frequency must be positive and finite"); return frequency_hz; } template diff --git a/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp b/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp index c48509f..e108590 100644 --- a/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp +++ b/Kernel/src/renderive/real_time_data/Attach_Real_Time_Data.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -28,7 +29,7 @@ struct With_Real_Time_Data { if (!std::apply([](const auto&... source) { return (static_cast(source) && ...); }, this->data)) { - throw std::invalid_argument("real-time data source is null"); + ::renderive::error::unexpected("real-time data source is null"); } } std::tuple...> data; diff --git a/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp b/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp index 854e7c0..6f19f52 100644 --- a/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp +++ b/Kernel/src/renderive/real_time_data/base/Real_Time_Data_Base.cpp @@ -1,3 +1,4 @@ +#include #include "Real_Time_Data_Base.hpp" #include "renderive/renderable/base/Renderable_Base_p.hpp" #include @@ -19,7 +20,7 @@ Real_Time_Data_Binding Real_Time_Data_Base::bind_renderable(Renderable_Base& ren { std::lock_guard lock(binding_mutex_); if (bound_renderable_) - throw std::logic_error("real-time data is already bound to a renderable"); + ::renderive::error::unexpected("real-time data is already bound to a renderable"); bound_renderable_ = &renderable; } try { @@ -27,7 +28,8 @@ Real_Time_Data_Binding Real_Time_Data_Base::bind_renderable(Renderable_Base& ren } catch (...) { std::lock_guard lock(binding_mutex_); bound_renderable_ = nullptr; - throw; + ::renderive::error::unexpected( + "binding real-time data", std::current_exception()); } return {renderable, *this}; } diff --git a/Kernel/src/renderive/render_graph/External_Operation.cpp b/Kernel/src/renderive/render_graph/External_Operation.cpp index 5579116..6eca803 100644 --- a/Kernel/src/renderive/render_graph/External_Operation.cpp +++ b/Kernel/src/renderive/render_graph/External_Operation.cpp @@ -1,3 +1,4 @@ +#include #include "External_Operation.hpp" #include #include @@ -22,7 +23,8 @@ public: { std::lock_guard lock(mutex_); if (failure_) - std::rethrow_exception(failure_); + ::renderive::error::unexpected( + "scheduling external operation deadline", failure_); ticket = ++sequence_; auto iterator = entries_.emplace(deadline, Entry{ticket, std::move(callback)}); tickets_.emplace(ticket, iterator); @@ -37,7 +39,8 @@ public: { std::lock_guard lock(mutex_); if (failure_) - std::rethrow_exception(failure_); + ::renderive::error::unexpected( + "cancelling external operation deadline", failure_); const auto found = tickets_.find(ticket); if (found == tickets_.end()) return; @@ -99,7 +102,9 @@ private: } } catch (...) { std::lock_guard lock(mutex_); - failure_ = std::current_exception(); + failure_ = ::renderive::error::capture( + "running external operation deadline service", + std::current_exception()); stopping_ = true; } } @@ -115,7 +120,7 @@ private: External_Operation_Error checked_cancellation_error(External_Operation_Error error) { if (error != External_Operation_Error::cancelled && error != External_Operation_Error::deadline_exceeded) - throw std::invalid_argument("external operation cancellation error is invalid"); + ::renderive::error::unexpected("external operation cancellation error is invalid"); return error; } } @@ -138,7 +143,8 @@ struct External_Operation::State { } catch (...) { terminal = External_Operation_Status::failed; terminal_error = External_Operation_Error::none; - terminal_exception = std::current_exception(); + terminal_exception = ::renderive::error::capture( + "finishing external operation", std::current_exception()); } } Completion callback; @@ -172,15 +178,15 @@ External_Operation::External_Operation(std::shared_ptr state) noexcept : state_(std::move(state)) {} void External_Operation::on_complete(Completion completion) const { if (!state_) - throw std::logic_error("external operation is empty"); + ::renderive::error::unexpected("external operation is empty"); if (!completion) - throw std::invalid_argument("external operation completion is empty"); + ::renderive::error::unexpected("external operation completion is empty"); External_Operation_Completion result; bool invoke{}; { std::lock_guard lock(state_->mutex); if (state_->subscribed) - throw std::logic_error("external operation already has a completion subscriber"); + ::renderive::error::unexpected("external operation already has a completion subscriber"); state_->subscribed = true; if (state_->status == External_Operation_Status::pending) { state_->completion = std::move(completion); @@ -218,12 +224,14 @@ bool External_Operation_Source::complete(std::function commit) { } bool External_Operation_Source::fail(External_Operation_Error error) { if (error != External_Operation_Error::external_failure) - throw std::invalid_argument("external operation failure error is invalid"); + ::renderive::error::unexpected("external operation failure error is invalid"); return state_ && state_->finish(External_Operation_Status::failed, error, {}); } bool External_Operation_Source::fail(std::exception_ptr exception) { if (!exception) - throw std::invalid_argument("external operation failure exception is empty"); + ::renderive::error::unexpected("external operation failure exception is empty"); + exception = ::renderive::error::capture( + "failing external operation", std::move(exception)); return state_ && state_->finish(External_Operation_Status::failed, External_Operation_Error::none, std::move(exception)); @@ -234,7 +242,7 @@ bool External_Operation_Source::cancel(External_Operation_Error error) { } void External_Operation_Source::set_deadline(Clock::time_point deadline) { if (!state_) - throw std::logic_error("external operation source is empty"); + ::renderive::error::unexpected("external operation source is empty"); Deadline_Service::Ticket previous_ticket{}; { std::lock_guard lock(state_->mutex); @@ -275,12 +283,12 @@ Node_Execution_Result Node_Execution_Result::completed() noexcept { } Node_Execution_Result Node_Execution_Result::failed(External_Operation_Error error) { if (error == External_Operation_Error::none) - throw std::invalid_argument("failed node result has no error"); + ::renderive::error::unexpected("failed node result has no error"); return Node_Execution_Result(error, std::nullopt); } Node_Execution_Result Node_Execution_Result::external(External_Operation operation) { if (!operation) - throw std::invalid_argument("external node result has no operation"); + ::renderive::error::unexpected("external node result has no operation"); return Node_Execution_Result(External_Operation_Error::none, std::move(operation)); } External_Operation_Error Node_Execution_Result::error() const noexcept { @@ -291,6 +299,6 @@ bool Node_Execution_Result::is_external() const noexcept { } const External_Operation& Node_Execution_Result::operation() const { if (!operation_) - throw std::logic_error("node result has no external operation"); + ::renderive::error::unexpected("node result has no external operation"); return *operation_; } diff --git a/Kernel/src/renderive/render_graph/Frame_Analysis.cpp b/Kernel/src/renderive/render_graph/Frame_Analysis.cpp index c2b1b59..f863f08 100644 --- a/Kernel/src/renderive/render_graph/Frame_Analysis.cpp +++ b/Kernel/src/renderive/render_graph/Frame_Analysis.cpp @@ -1,3 +1,4 @@ +#include #include "Frame_Analysis.hpp" #include #include @@ -131,9 +132,9 @@ Node_Statistics statistics_for(Render_Node_Id id, const Node_Samples& samples) { Frame_Analysis analyze_frame(const Render_Plan& plan, const Frame_Snapshot& frame) { if (frame.render_plan_version != plan.version) - throw std::invalid_argument("frame and render plan versions differ"); + ::renderive::error::unexpected("frame and render plan versions differ"); if (frame.node_executions.size() != plan.graph.nodes.size()) - throw std::invalid_argument("frame execution slot count differs from render plan"); + ::renderive::error::unexpected("frame execution slot count differs from render plan"); Frame_Analysis result; result.frame_id = frame.frame_id; @@ -146,7 +147,7 @@ Frame_Analysis analyze_frame(const Render_Plan& plan, const Frame_Snapshot& fram indices.emplace(node.node_id, node.execution_index); const auto& execution = frame.node_executions.at(node.execution_index); if (execution.node_id != node.node_id) - throw std::invalid_argument("frame execution slot has a different node id"); + ::renderive::error::unexpected("frame execution slot has a different node id"); auto& analysis = result.nodes[node.execution_index]; analysis.node_id = node.node_id; analysis.duration_ns = execution.duration_ns(); @@ -209,7 +210,7 @@ Frame_Analysis analyze_frame(const Render_Plan& plan, const Frame_Snapshot& fram } } if (topological.size() != plan.graph.nodes.size()) - throw std::invalid_argument("render plan contains a cycle"); + ::renderive::error::unexpected("render plan contains a cycle"); std::vector path_duration(plan.graph.nodes.size()); std::vector path_parent(plan.graph.nodes.size(), diff --git a/Kernel/src/renderive/render_graph/Render_Plan.cpp b/Kernel/src/renderive/render_graph/Render_Plan.cpp index c77da3b..2f282a4 100644 --- a/Kernel/src/renderive/render_graph/Render_Plan.cpp +++ b/Kernel/src/renderive/render_graph/Render_Plan.cpp @@ -1,3 +1,4 @@ +#include #include "Render_Plan.hpp" #include #include @@ -28,7 +29,7 @@ void validate_graph(const Render_Graph& graph) { for (std::size_t index = 0; index < graph.nodes.size(); ++index) { const auto id = graph.nodes[index].node_id; if (id == 0 || !indices.emplace(id, index).second) - throw std::invalid_argument("render graph has an invalid or duplicate node id"); + ::renderive::error::unexpected("render graph has an invalid or duplicate node id"); } std::vector> successors(graph.nodes.size()); std::vector indegree(graph.nodes.size()); @@ -36,9 +37,9 @@ void validate_graph(const Render_Graph& graph) { const auto from = indices.find(edge.from); const auto to = indices.find(edge.to); if (from == indices.end() || to == indices.end()) - throw std::invalid_argument("render graph edge references an unknown node"); + ::renderive::error::unexpected("render graph edge references an unknown node"); if (from->second == to->second) - throw std::invalid_argument("render graph cycle"); + ::renderive::error::unexpected("render graph cycle"); successors[from->second].push_back(to->second); ++indegree[to->second]; } @@ -55,7 +56,7 @@ void validate_graph(const Render_Graph& graph) { } } if (ready.size() != graph.nodes.size()) - throw std::invalid_argument("render graph cycle"); + ::renderive::error::unexpected("render graph cycle"); } struct Render_Edge_Hash { @@ -85,9 +86,9 @@ Render_Graph_Builder::Task Render_Graph_Builder::emplace( Render_Node_Id node_id, std::uint64_t owner_id, std::string name, Render_Node_Kind kind) { if (node_id == 0) - throw std::invalid_argument("render node id must not be zero"); + ::renderive::error::unexpected("render node id must not be zero"); if (!node_ids_.insert(node_id).second) - throw std::invalid_argument("duplicate render node id"); + ::renderive::error::unexpected("duplicate render node id"); graph_.nodes.push_back({node_id, owner_id, std::move(name), kind, graph_.nodes.size()}); return {this, generation_, graph_.nodes.size() - 1}; @@ -97,7 +98,7 @@ void Render_Graph_Builder::precede(Task from, Task to) { validate(from); validate(to); if (from.index == to.index) - throw std::invalid_argument("render graph cycle"); + ::renderive::error::unexpected("render graph cycle"); const Render_Edge edge{graph_.nodes[from.index].node_id, graph_.nodes[to.index].node_id}; if (successors_[edge.from].insert(edge.to).second) graph_.edges.push_back(edge); @@ -117,7 +118,7 @@ Render_Graph Render_Graph_Builder::finish() && { void Render_Graph_Builder::validate(Task task) const { if (task.builder != this || task.generation != generation_ || task.index >= graph_.nodes.size()) - throw std::invalid_argument("invalid render graph task"); + ::renderive::error::unexpected("invalid render graph task"); } std::shared_ptr Render_Plan_History::publish(Render_Graph graph) { @@ -127,7 +128,7 @@ std::shared_ptr Render_Plan_History::publish(Render_Graph gra if (!plans_.empty() && same_topology(plans_.back()->graph, graph)) return plans_.back(); if (next_version_ == 0 || next_version_ == std::numeric_limits::max()) - throw std::overflow_error("render plan version exhausted"); + ::renderive::error::unexpected("render plan version exhausted"); auto plan = std::make_shared(); plan->version = next_version_++; plan->graph = std::move(graph); diff --git a/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp b/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp index 3ad03c1..1c3c0ff 100644 --- a/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp +++ b/Kernel/src/renderive/render_graph/detail/Render_Graph_Runtime.cpp @@ -1,3 +1,4 @@ +#include #include "Render_Graph_Runtime.hpp" #include #include @@ -21,7 +22,7 @@ Render_Graph_Execution_Error graph_error(External_Operation_Error error) { case External_Operation_Error::external_failure: return Render_Graph_Execution_Error::external_failure; } - throw std::logic_error("unknown external operation error"); + ::renderive::error::unexpected("unknown external operation error"); } } struct Render_Graph_Runtime::State @@ -72,7 +73,7 @@ struct Render_Graph_Runtime::State { std::lock_guard lock(lifecycle_mutex); if (running.load(std::memory_order_acquire)) - throw std::logic_error("render graph runtime is already executing"); + ::renderive::error::unexpected("render graph runtime is already executing"); cancellation_requested.store(false, std::memory_order_release); cancellation_error = External_Operation_Error::cancelled; running.store(true, std::memory_order_release); @@ -81,10 +82,10 @@ struct Render_Graph_Runtime::State External_Operation_Error execution_error{}; try { if (!execute) - throw std::invalid_argument("render graph node executor is empty"); + ::renderive::error::unexpected("render graph node executor is empty"); if (!execution_slots.empty() && execution_slots.size() != execution_count) - throw std::invalid_argument("render graph execution slot count differs from plan"); + ::renderive::error::unexpected("render graph execution slot count differs from plan"); graph.reset(); executions.assign(execution_slots.begin(), execution_slots.end()); if (executions.empty()) @@ -105,7 +106,7 @@ struct Render_Graph_Runtime::State arena.execute([this] { for (auto& node : nodes) { if (node.root && !node.ready->try_put(Message{})) - throw std::logic_error( + ::renderive::error::unexpected( "render graph rejected root execution message"); } graph.wait_for_all(); @@ -116,11 +117,14 @@ struct Render_Graph_Runtime::State execution_error = first_error; } } catch (...) { - execution_exception = std::current_exception(); + execution_exception = ::renderive::error::capture( + "coordinating render graph execution", + std::current_exception()); } finish_execution(); if (execution_exception) - std::rethrow_exception(execution_exception); + ::renderive::error::unexpected( + "executing render graph", execution_exception); return graph_error(execution_error); } void finish_execution() { @@ -140,7 +144,7 @@ struct Render_Graph_Runtime::State } void cancel_pending(External_Operation_Error error) { if (error == External_Operation_Error::none) - throw std::invalid_argument("render graph cancellation error is none"); + ::renderive::error::unexpected("render graph cancellation error is none"); { std::lock_guard lifecycle_lock(lifecycle_mutex); if (!running.load(std::memory_order_acquire)) @@ -183,7 +187,11 @@ struct Render_Graph_Runtime::State try { result = execute_node(index, metrics); } catch (...) { - fail_exception(index, std::current_exception(), render_clock_now_ns()); + fail_exception( + index, + ::renderive::error::capture( + "executing render graph node", std::current_exception()), + render_clock_now_ns()); return; } const std::uint64_t cpu_end = render_clock_now_ns(); @@ -235,17 +243,31 @@ struct Render_Graph_Runtime::State static_cast(gateway_ptr->try_put(Message{})); } } catch (...) { - self->fail_exception(index, std::current_exception(), end); + self->fail_exception( + index, + ::renderive::error::capture( + "completing external render graph node", + std::current_exception()), + end); } gateway_ptr->release_wait(); }); } catch (...) { clear_external(index); - fail_exception(index, std::current_exception(), render_clock_now_ns()); + fail_exception( + index, + ::renderive::error::capture( + "subscribing external render graph node", + std::current_exception()), + render_clock_now_ns()); gateway.release_wait(); } } catch (...) { - fail_exception(index, std::current_exception(), render_clock_now_ns()); + fail_exception( + index, + ::renderive::error::capture( + "running render graph node", std::current_exception()), + render_clock_now_ns()); } } void clear_external(std::size_t index) { diff --git a/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp b/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp index 5998038..751fd02 100644 --- a/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp +++ b/Kernel/src/renderive/renderable/Renderable_Graph_Builder.cpp @@ -1,3 +1,4 @@ +#include #include "Renderable_Graph_Builder.hpp" #include #include @@ -6,7 +7,7 @@ Renderable_Graph_Builder::Renderable_Graph_Builder( std::uint64_t owner_id, Node_Id_Resolver resolve_node_id) : owner_id_(owner_id), resolve_node_id_(std::move(resolve_node_id)) { if (owner_id_ == 0 || !resolve_node_id_) - throw std::invalid_argument("renderable graph builder requires an owner and id resolver"); + ::renderive::error::unexpected("renderable graph builder requires an owner and id resolver"); } Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace( @@ -29,11 +30,11 @@ Renderable_Graph_Builder::Task Renderable_Graph_Builder::emplace_function( std::string logical_key, std::string name, Render_Node_Kind kind, Render_Node_Function function) { if (logical_key.empty()) - throw std::invalid_argument("render node logical key must not be empty"); + ::renderive::error::unexpected("render node logical key must not be empty"); if (tasks_.contains(logical_key)) - throw std::invalid_argument("duplicate render node logical key"); + ::renderive::error::unexpected("duplicate render node logical key"); if (std::visit([](const auto& value) { return !value; }, function)) - throw std::invalid_argument("render node function must not be empty"); + ::renderive::error::unexpected("render node function must not be empty"); const Render_Node_Id id = resolve_node_id_(logical_key); const Task task = builder_.emplace(id, owner_id_, std::move(name), kind); functions_.emplace(id, std::move(function)); @@ -49,7 +50,7 @@ Renderable_Graph_Builder::Task Renderable_Graph_Builder::find( std::string_view logical_key) const { const auto iterator = tasks_.find(std::string(logical_key)); if (iterator == tasks_.end()) - throw std::out_of_range("render node logical key"); + ::renderive::error::unexpected("render node logical key"); return iterator->second; } diff --git a/Kernel/src/renderive/renderable/Renderive_Id_Allocator.cpp b/Kernel/src/renderive/renderable/Renderive_Id_Allocator.cpp index 2f78017..3323ade 100644 --- a/Kernel/src/renderive/renderable/Renderive_Id_Allocator.cpp +++ b/Kernel/src/renderive/renderable/Renderive_Id_Allocator.cpp @@ -1,3 +1,4 @@ +#include #include "Renderive_Id_Allocator.hpp" #include #include @@ -8,7 +9,7 @@ Id allocate_id(std::atomic& next, const char* message) { Id value = next.load(std::memory_order_relaxed); for (;;) { if (value == 0 || value == std::numeric_limits::max()) - throw std::overflow_error(message); + ::renderive::error::unexpected(message); if (next.compare_exchange_weak(value, value + 1, std::memory_order_relaxed)) return value; } diff --git a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp index d3bfcf6..694a9e3 100644 --- a/Kernel/src/renderive/renderable/base/Renderable_Base.cpp +++ b/Kernel/src/renderive/renderable/base/Renderable_Base.cpp @@ -1,3 +1,4 @@ +#include #include "Renderable_Base_p.hpp" #include @@ -62,7 +63,7 @@ void Renderable_Base::Impl::bind_scene( if (!current) current = std::move(lifetime); else if (current != lifetime) - throw std::invalid_argument("renderable belongs to another scene"); + ::renderive::error::unexpected("renderable belongs to another scene"); } void Renderable_Base::Impl::rebuild_render_graph() { @@ -90,7 +91,7 @@ Renderable_Base::Impl::render_graph_snapshot() { [this, &next_identities](std::string_view key) { const std::string logical_key(key); if (next_identities.contains(logical_key)) - throw std::invalid_argument( + ::renderive::error::unexpected( "duplicate render node logical key"); const auto current = node_identities.find(logical_key); const Render_Node_Id node_id = @@ -118,10 +119,10 @@ Renderable_Base::Impl::memory_resource() const noexcept { Scene_Base& Renderable_Base::Impl::scene() const { const auto lifetime = real_time_data_state->scene_lifetime; if (!lifetime) - throw std::logic_error("renderable is not bound to a scene"); + ::renderive::error::unexpected("renderable is not bound to a scene"); auto lease = lifetime->acquire(); if (!lease) - throw std::logic_error("renderable scene is no longer alive"); + ::renderive::error::unexpected("renderable scene is no longer alive"); return lease.scene(); } @@ -180,7 +181,7 @@ void Renderable_Base::Impl::capture_frame_data(Frame_Render_Snapshot&) const {} void Renderable_Base::Impl::add_prepare_action(Prepare_Action action) { if (!action) - throw std::invalid_argument("renderable prepare action is empty"); + ::renderive::error::unexpected("renderable prepare action is empty"); prepare_actions.push_back(std::move(action)); } @@ -242,7 +243,7 @@ Renderable_Base::Renderable_Base(std::unique_ptr implementation, std::pmr::memory_resource& memory_resource) : d_ptr(std::move(implementation)) { if (!d_ptr) - throw std::invalid_argument("renderable implementation is null"); + ::renderive::error::unexpected("renderable implementation is null"); d_ptr->initialize(*this, configuration, memory_resource); } diff --git a/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp b/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp index ab9ebe2..57e430c 100644 --- a/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp +++ b/Kernel/src/renderive/scene/base/Frame_Render_Snapshot.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -57,7 +58,7 @@ public: [[nodiscard]] const State& scene_state() const { const auto* value = std::any_cast(&scene_state_); if (!value) - throw std::logic_error("frame scene state type mismatch"); + ::renderive::error::unexpected("frame scene state type mismatch"); return *value; } @@ -69,11 +70,11 @@ public: template void capture(Renderable_Id owner, std::shared_ptr data) { if (!data) - throw std::invalid_argument("captured render data is null"); + ::renderive::error::unexpected("captured render data is null"); const auto [entry, inserted] = captured_.try_emplace( owner, Typed_Data{typeid(Data), std::move(data)}); if (!inserted) - throw std::logic_error( + ::renderive::error::unexpected( "renderable captured frame data more than once"); } @@ -84,7 +85,7 @@ public: if (entry == captured_.end()) return {}; if (entry->second.type != std::type_index(typeid(Data))) - throw std::logic_error("captured render data type mismatch"); + ::renderive::error::unexpected("captured render data type mismatch"); return std::static_pointer_cast(entry->second.value); } @@ -92,12 +93,12 @@ public: void publish_prepared(Renderable_Id owner, std::shared_ptr data) const { if (!data) - throw std::invalid_argument("prepared render data is null"); + ::renderive::error::unexpected("prepared render data is null"); std::lock_guard lock(prepared_mutex_); const auto [entry, inserted] = prepared_.try_emplace( owner, Typed_Data{typeid(Data), std::move(data)}); if (!inserted) - throw std::logic_error( + ::renderive::error::unexpected( "renderable published prepared data more than once"); } @@ -109,7 +110,7 @@ public: if (entry == prepared_.end()) return {}; if (entry->second.type != std::type_index(typeid(Data))) - throw std::logic_error("prepared render data type mismatch"); + ::renderive::error::unexpected("prepared render data type mismatch"); return std::static_pointer_cast(entry->second.value); } diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index 4fe5c9e..3db2a99 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -1,3 +1,4 @@ +#include #include "Scene_Base.hpp" #include @@ -50,7 +51,7 @@ Scene_Edit_Error relationship_error( return display ? Scene_Edit_Error::display_cycle : Scene_Edit_Error::dependency_cycle; } - throw std::logic_error("unknown scene relationship mutation error"); + ::renderive::error::unexpected("unknown scene relationship mutation error"); } Scene_Render_Error scene_render_error( renderive::render_graph::detail::Render_Graph_Execution_Error error) { @@ -65,7 +66,7 @@ Scene_Render_Error scene_render_error( case Error::external_failure: return Scene_Render_Error::external_failure; } - throw std::logic_error("unknown render graph execution error"); + ::renderive::error::unexpected("unknown render graph execution error"); } } @@ -80,7 +81,9 @@ public: execute_available(); } catch (...) { scheduled_.store(false, std::memory_order_release); - scene_.record_pending_exception(std::current_exception()); + scene_.record_pending_exception(::renderive::error::capture( + "executing queued scene operation", + std::current_exception())); } return Message{}; }) { @@ -102,7 +105,7 @@ public: const std::size_t queued = queued_.fetch_add(1, std::memory_order_relaxed) + 1; update_peak(peak_queued_, queued); if (!operations_.try_push(std::move(operation))) - throw std::logic_error("scene execution queue rejected admitted operation"); + ::renderive::error::unexpected("scene execution queue rejected admitted operation"); schedule(); } void wait() { @@ -131,7 +134,7 @@ private: const bool accepted = arena.execute([this] { return node_.try_put(Message{}); }); if (!accepted) { scheduled_.store(false, std::memory_order_release); - throw std::logic_error("scene execution graph rejected work"); + ::renderive::error::unexpected("scene execution graph rejected work"); } } void execute_available() { @@ -270,7 +273,7 @@ Scene_Base::~Scene_Base() { Scene_Base::Attach_Builder::Attach_Builder(Scene_Base& scene) : scene_(scene), task_lock_(scene.task_mutex_) { if (scene.runtime_started_) - throw std::logic_error("scene attach builder is only available before runtime starts"); + ::renderive::error::unexpected("scene attach builder is only available before runtime starts"); } Scene_Edit_Error Scene_Base::Attach_Builder::attach(Renderable renderable) { std::lock_guard lock(scene_.model_mutex_); @@ -314,7 +317,7 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::attach(Renderable renderable) { if (const auto existing = scene_.renderables_.find(id); existing != scene_.renderables_.end()) { if (existing->second != renderable) - throw std::logic_error("renderable id collision"); + ::renderive::error::unexpected("renderable id collision"); return Scene_Edit_Error::none; } auto cache = scene_.raster_capabilities_ @@ -326,13 +329,14 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::attach(Renderable renderable) { if (scene_.raster_capabilities_) scene_.raster_capabilities_->attach(id); if (!scene_.renderables_.try_emplace(id, renderable).second) - throw std::logic_error("renderable id collision"); + ::renderive::error::unexpected("renderable id collision"); try { if (cache && !scene_.color_caches_.try_emplace(id, std::move(cache)).second) - throw std::logic_error("renderable color cache collision"); + ::renderive::error::unexpected("renderable color cache collision"); } catch (...) { scene_.renderables_.erase(id); - throw; + ::renderive::error::unexpected( + "attaching renderable", std::current_exception()); } renderable_data.real_time_data_state->attached.store(true, std::memory_order_release); renderable_data.invalidate_prepare(); @@ -435,7 +439,7 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::clear_dependency_parent( } Scene_Edit_Error Scene_Base::Edit_Operation::wait() const { if (!result_.valid()) - throw std::logic_error("scene edit operation is invalid"); + ::renderive::error::unexpected("scene edit operation is invalid"); return result_.get(); } Scene_Base::Edit_Operation Scene_Base::edit_renderables(Renderable_Edit edit) { @@ -630,7 +634,8 @@ Scene_Render_Error Scene_Base::submit_render(Abstract_Frame* frame) { } if (previous_exception) { task_lock.unlock(); - std::rethrow_exception(previous_exception); + ::renderive::error::unexpected( + "notifying render failure", previous_exception); } auto& strategy = frame_control_strategy(); strategy.swap(); @@ -641,7 +646,7 @@ Scene_Render_Error Scene_Base::submit_render(Abstract_Frame* frame) { snapshot = capture_live_frame(); snapshot->next_refresh_interval_ns_ = strategy.frame_control_state().next_refresh_interval_ns; if (render_sequence_ == std::numeric_limits::max()) - throw std::overflow_error("render sequence exhausted"); + ::renderive::error::unexpected("render sequence exhausted"); snapshot->render_sequence = ++render_sequence_; auto task = std::make_shared(); task->frame = std::make_shared(); @@ -665,7 +670,8 @@ Scene_Render_Error Scene_Base::submit_render(Abstract_Frame* frame) { d_func().dispatch(submitted_observation); } catch (...) { active_submitted_observer_scene_ = previous_observer_scene; - const auto exception = std::current_exception(); + const auto exception = ::renderive::error::capture( + "notifying submitted scene render", std::current_exception()); { std::lock_guard lock(task_mutex_); task->completion->exception = exception; @@ -675,7 +681,8 @@ Scene_Render_Error Scene_Base::submit_render(Abstract_Frame* frame) { } capture_controller_.finish_frame(capture_ticket, false); complete_pending_operation(); - std::rethrow_exception(exception); + ::renderive::error::unexpected( + "waiting for submitted render", exception); } active_submitted_observer_scene_ = previous_observer_scene; try { @@ -685,7 +692,8 @@ Scene_Render_Error Scene_Base::submit_render(Abstract_Frame* frame) { }); } catch (...) { capture_controller_.finish_frame(capture_ticket, false); - const auto exception = std::current_exception(); + const auto exception = ::renderive::error::capture( + "scheduling scene render", std::current_exception()); { std::lock_guard lock(task_mutex_); current_completion_->exception = exception; @@ -693,7 +701,8 @@ Scene_Render_Error Scene_Base::submit_render(Abstract_Frame* frame) { current_completion_->observed = true; } complete_pending_operation(); - std::rethrow_exception(exception); + ::renderive::error::unexpected( + "waiting for render completion", exception); } std::size_t deferred_render_count{}; { @@ -722,7 +731,8 @@ Scene_Render_Error Scene_Base::wait_for_render() { const auto error = completion->error; lock.unlock(); if (exception) - std::rethrow_exception(exception); + ::renderive::error::unexpected( + "waiting for scene idle", exception); return error; } Scene_Base::Edit_Operation Scene_Base::enqueue_renderable_edit( @@ -744,7 +754,9 @@ Scene_Base::Edit_Operation Scene_Base::enqueue_renderable_edit( try { promise->set_value(execute_renderable_edit(std::move(edit))); } catch (...) { - const auto exception = std::current_exception(); + const auto exception = ::renderive::error::capture( + "executing asynchronous renderable edit", + std::current_exception()); record_pending_exception(exception); promise->set_exception(exception); } @@ -752,7 +764,8 @@ Scene_Base::Edit_Operation Scene_Base::enqueue_renderable_edit( }); } catch (...) { complete_pending_operation(); - throw; + ::renderive::error::unexpected( + "observing scene render submission", std::current_exception()); } return operation; } @@ -761,7 +774,7 @@ void Scene_Base::submit_operation(std::function operation) { { std::lock_guard lock(task_mutex_); if (shutting_down_) - throw std::logic_error("scene is shutting down"); + ::renderive::error::unexpected("scene is shutting down"); runtime_started_ = true; ++pending_operations_; } @@ -770,13 +783,16 @@ void Scene_Base::submit_operation(std::function operation) { try { operation(); } catch (...) { - record_pending_exception(std::current_exception()); + record_pending_exception(::renderive::error::capture( + "executing asynchronous scene operation", + std::current_exception())); } complete_pending_operation(); }); } catch (...) { complete_pending_operation(); - throw; + ::renderive::error::unexpected( + "submitting scene render", std::current_exception()); } } void Scene_Base::complete_pending_operation() { @@ -811,24 +827,24 @@ Scene_Edit_Error Scene_Base::cleanup_detached_topology_locked() { Scene_Edit_Error Scene_Base::validate_structure_locked() const { for (const auto& [id, renderable] : renderables_) { if (!renderable) - throw std::logic_error("scene contains null renderable owner"); + ::renderive::error::unexpected("scene contains null renderable owner"); if (renderable_scene_error(*renderable) != Scene_Edit_Error::none) - throw std::logic_error("scene contains renderable bound to another scene"); + ::renderive::error::unexpected("scene contains renderable bound to another scene"); if (!renderable->d_func().real_time_data_state->attached.load( std::memory_order_acquire)) - throw std::logic_error("scene contains renderable whose attached state is false"); + ::renderive::error::unexpected("scene contains renderable whose attached state is false"); if (!dependency_resolver_.contains(id)) - throw std::logic_error("dependency graph is missing attached renderable " + std::to_string(id)); + ::renderive::error::unexpected("dependency graph is missing attached renderable " + std::to_string(id)); } const auto dependency = dependency_resolver_.resolve(); if (dependency.order.size() != renderables_.size()) - throw std::logic_error("dependency graph does not match attached renderables"); + ::renderive::error::unexpected("dependency graph does not match attached renderables"); for (const Renderable_Id id : dependency.order) { if (!renderables_.contains(id)) - throw std::logic_error("dependency graph references detached renderable " + std::to_string(id)); + ::renderive::error::unexpected("dependency graph references detached renderable " + std::to_string(id)); } if (raster_capabilities_ && !raster_capabilities_->valid()) - throw std::logic_error("display graph does not match attached renderables"); + ::renderive::error::unexpected("display graph does not match attached renderables"); return Scene_Edit_Error::none; } @@ -840,7 +856,7 @@ void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) { return; } if (renderable_scene_error(renderable) != Scene_Edit_Error::none) - throw std::logic_error("attached renderable scene invariant violated"); + ::renderive::error::unexpected("attached renderable scene invariant violated"); if (active_renderable_edit_scene_ == this) { renderable_data.reset_render_graph(); notify_model_dirty(); @@ -1076,7 +1092,8 @@ Capture_Request_Result Scene_Base::capture_frames(std::size_t count) { capture_repository_.begin_session(request.session_id, count); } catch (...) { capture_controller_.cancel(request.session_id); - throw; + ::renderive::error::unexpected( + "executing renderable edit", std::current_exception()); } return request; } @@ -1141,7 +1158,7 @@ const Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy() const { void Scene_Base::bind_raster_capabilities( renderive::scene::detail::Raster_Capabilities& capabilities) { if (raster_capabilities_ != nullptr) - throw std::logic_error("scene raster capabilities are already bound"); + ::renderive::error::unexpected("scene raster capabilities are already bound"); raster_capabilities_ = &capabilities; } @@ -1183,9 +1200,9 @@ std::uint64_t Scene_Base::Impl::now_ns() const noexcept { std::unique_lock Scene_Base::lock_render_idle() { if (is_render_execution_context()) - throw std::logic_error("render-idle operation is not allowed during render execution"); + ::renderive::error::unexpected("render-idle operation is not allowed during render execution"); if (active_submitted_observer_scene_ == this) - throw std::logic_error("render-idle operation is not allowed during render submission observation"); + ::renderive::error::unexpected("render-idle operation is not allowed during render submission observation"); std::unique_lock lock(task_mutex_); render_completed_.wait(lock, [this] { return pending_operations_ == 0; }); return lock; @@ -1243,7 +1260,8 @@ Scene_Edit_Error Scene_Base::execute_renderable_edit( transaction.rollback(); active_renderable_edit_scene_ = previous; active_edit_transaction_ = nullptr; - std::rethrow_exception(exception); + ::renderive::error::unexpected( + "executing scene render task", exception); } } @@ -1266,7 +1284,8 @@ void Scene_Base::execute_render_task(std::shared_ptr task) { snapshot.scene_state_revision_, snapshot.renderables.size(), task->topology, task->snapshot, task->compiled_plan->plan}); } catch (...) { - exception = std::current_exception(); + exception = ::renderive::error::capture( + "executing scene render task", std::current_exception()); if (!render_graph_entered) capture_controller_.finish_frame(snapshot.capture_ticket_, false); try { @@ -1274,8 +1293,9 @@ void Scene_Base::execute_render_task(std::shared_ptr task) { snapshot.scene_state_revision_, snapshot.renderables.size(), task->topology, task->snapshot, task->compiled_plan->plan}); } catch (...) { - exception = std::make_exception_ptr(Render_Failure_Notification_Error( - std::move(exception), std::current_exception())); + exception = ::renderive::error::capture< + Render_Failure_Notification_Error>( + std::move(exception), std::current_exception()); } } { @@ -1345,10 +1365,10 @@ std::shared_ptr Scene_Base::compile_render_pla for (auto& item : active) { const auto& state = snapshot.renderables[item.snapshot_index]; if (!state.render_graph) - throw std::logic_error("frame renderable has no graph snapshot"); + ::renderive::error::unexpected("frame renderable has no graph snapshot"); for (const auto& node : state.render_graph->graph.nodes) { if (node.kind == Render_Node_Kind::composite) - throw std::logic_error( + ::renderive::error::unexpected( "renderable graph must not define composite nodes"); graph.nodes.push_back(node); const auto& render_function = @@ -1370,7 +1390,7 @@ std::shared_ptr Scene_Base::compile_render_pla const auto from = local_kinds.find(edge.from); const auto to = local_kinds.find(edge.to); if (from == local_kinds.end() || to == local_kinds.end()) - throw std::logic_error( + ::renderive::error::unexpected( "renderable graph edge references an unknown node"); if (from->second == Render_Node_Kind::prepare && to->second == Render_Node_Kind::prepare) { @@ -1466,7 +1486,7 @@ std::shared_ptr Scene_Base::compile_render_pla for (const auto& node : plan->graph.nodes) { auto function = functions.find(node.node_id); if (function == functions.end()) - throw std::logic_error("render plan node has no execution binding"); + ::renderive::error::unexpected("render plan node has no execution binding"); execution_bindings[node.execution_index] = std::move(function->second); } if (compiled_render_plan_ && compiled_render_plan_->plan == plan) { @@ -1486,7 +1506,7 @@ std::shared_ptr Scene_Base::compile_render_pla Scene_Render_Error Scene_Base::execute_render_graph(Render_Task& task) { if (!task.snapshot || !task.compiled_plan || !task.frame) - throw std::logic_error("render task is incomplete"); + ::renderive::error::unexpected("render task is incomplete"); auto& frame = *task.frame; const auto& snapshot = *task.snapshot; const Capture_Frame_Ticket capture_ticket = snapshot.capture_ticket_; @@ -1537,7 +1557,7 @@ Scene_Render_Error Scene_Base::execute_render_graph(Render_Task& task) { if (!state.paint_required) return Node_Execution_Result::completed(); if (!state.paint_buffer_) - throw std::logic_error( + ::renderive::error::unexpected( "paint node has no frame color cache"); std::get(binding.function)( Paint_Render_Context{snapshot, state, @@ -1563,7 +1583,7 @@ Scene_Render_Error Scene_Base::execute_render_graph(Render_Task& task) { return std::get(binding.function)( Scene_Render_Context{snapshot, metrics, diagnostics}); } - throw std::logic_error("unknown render node kind"); + ::renderive::error::unexpected("unknown render node kind"); }); if (graph_error != renderive::render_graph::detail::Render_Graph_Execution_Error::none) { Abstract_Frame::discard_render(frame); @@ -1601,7 +1621,8 @@ Scene_Render_Error Scene_Base::execute_render_graph(Render_Task& task) { if (frame_active) Abstract_Frame::discard_render(frame); capture_controller_.finish_frame(capture_ticket, false); - throw; + ::renderive::error::unexpected( + "notifying scene render completion", std::current_exception()); } } diff --git a/Kernel/src/renderive/scene/dependency/Dependency_Resolver.hpp b/Kernel/src/renderive/scene/dependency/Dependency_Resolver.hpp index 85170d4..be1f9c0 100644 --- a/Kernel/src/renderive/scene/dependency/Dependency_Resolver.hpp +++ b/Kernel/src/renderive/scene/dependency/Dependency_Resolver.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -45,12 +46,12 @@ public: for (const auto& relationship : resolution.relationships) { const auto result = add_parent(relationship.child, relationship.parent); if (!result) - throw std::logic_error("invalid dependency resolution"); + ::renderive::error::unexpected("invalid dependency resolution"); } } void attach(Id id) { if (!nodes_.try_emplace(id, resource()).second) - throw std::logic_error("dependency id is already attached"); + ::renderive::error::unexpected("dependency id is already attached"); } void erase(Id id) { validate_endpoint(id); @@ -155,7 +156,7 @@ public: } } if (result.order.size() != nodes_.size()) - throw std::logic_error("dependency graph contains a cycle"); + ::renderive::error::unexpected("dependency graph contains a cycle"); return result; } private: @@ -170,7 +171,7 @@ private: } void validate_endpoint(Id id) const { if (!nodes_.contains(id)) - throw std::logic_error("dependency endpoint invariant violated"); + ::renderive::error::unexpected("dependency endpoint invariant violated"); } [[nodiscard]] bool acyclic() const { std::unordered_map indegree; diff --git a/Kernel/tests/renderive/error/Error_Policy_Test.cpp b/Kernel/tests/renderive/error/Error_Policy_Test.cpp new file mode 100644 index 0000000..c019044 --- /dev/null +++ b/Kernel/tests/renderive/error/Error_Policy_Test.cpp @@ -0,0 +1,35 @@ +#include + +#include + +#include + +namespace renderive::error { +namespace { + +TEST(ErrorPolicy, ExceptionModePreservesExceptionType) { + set_mode(Mode::exception); + + EXPECT_THROW(unexpected("invalid value"), + std::invalid_argument); +} + +TEST(ErrorPolicy, FastFailModeTerminatesAtThePolicyBoundary) { + EXPECT_DEATH( + { + set_mode(Mode::fast_fail); + unexpected("broken invariant"); + }, + "broken invariant"); +} + +TEST(ErrorPolicy, CaptureUsesTheSameMode) { + set_mode(Mode::exception); + const auto exception = capture("async failure"); + + ASSERT_TRUE(exception); + EXPECT_THROW(std::rethrow_exception(exception), std::logic_error); +} + +} // namespace +} // namespace renderive::error diff --git a/Qt/CMakeLists.txt b/Qt/CMakeLists.txt index f32500c..6e77bc6 100644 --- a/Qt/CMakeLists.txt +++ b/Qt/CMakeLists.txt @@ -79,7 +79,9 @@ if (RENDERIVE_BUILD_TESTS) "QT_QPA_PLATFORM_PLUGIN_PATH=${Renderive_Qt_runtime_root}/plugins/platforms" "$" ) - set_tests_properties("${Renderive_Qt_test_target}" PROPERTIES LABELS "Renderive_Qt") + set_tests_properties("${Renderive_Qt_test_target}" PROPERTIES + LABELS "Renderive_Qt" + ENVIRONMENT "RENDERIVE_ERROR_MODE=exception") list(APPEND Renderive_Qt_test_targets "${Renderive_Qt_test_target}") endforeach () add_custom_target(Renderive_Qt_check diff --git a/render_2D/CMakeLists.txt b/render_2D/CMakeLists.txt index e6b4871..e754abc 100644 --- a/render_2D/CMakeLists.txt +++ b/render_2D/CMakeLists.txt @@ -56,7 +56,9 @@ if (RENDERIVE_BUILD_TESTS) target_link_libraries("${Renderive_render_2D_test_target}" PRIVATE Renderive_render_2D GTest::gtest_main) renderive_stage_kernel_runtime("${Renderive_render_2D_test_target}") add_test(NAME "${Renderive_render_2D_test_target}" COMMAND "${Renderive_render_2D_test_target}") - set_tests_properties("${Renderive_render_2D_test_target}" PROPERTIES LABELS "Renderive_render_2D") + set_tests_properties("${Renderive_render_2D_test_target}" PROPERTIES + LABELS "Renderive_render_2D" + ENVIRONMENT "RENDERIVE_ERROR_MODE=exception") list(APPEND Renderive_render_2D_test_targets "${Renderive_render_2D_test_target}") endforeach () add_custom_target(Renderive_render_2D_check diff --git a/render_2D/render_2D/axis/Numeric_Axis.h b/render_2D/render_2D/axis/Numeric_Axis.h index 8819292..45d974d 100644 --- a/render_2D/render_2D/axis/Numeric_Axis.h +++ b/render_2D/render_2D/axis/Numeric_Axis.h @@ -1,4 +1,5 @@ #pragma once +#include #include "Axis_State_Strategy.hpp" #include #include @@ -7,7 +8,7 @@ namespace renderive { struct Axis_Coordinate_Range_Validator { void operator()(const Range& value) const { if (!std::isfinite(value.origin) || !std::isfinite(value.target) || !(value.size() > 0.0)) - throw std::invalid_argument("axis coordinate range must be finite and non-empty"); + ::renderive::error::unexpected("axis coordinate range must be finite and non-empty"); } }; struct Axis_Properties : Axis_Base_Properties { diff --git a/render_2D/render_2D/scene/Plot_Scene.cpp b/render_2D/render_2D/scene/Plot_Scene.cpp index 01c3165..39cddbf 100644 --- a/render_2D/render_2D/scene/Plot_Scene.cpp +++ b/render_2D/render_2D/scene/Plot_Scene.cpp @@ -1,3 +1,4 @@ +#include #include "Plot_Scene.h" #include "detail/Plot_Scene_Model.hpp" #include "../plottable/Performance_Overlay.h" @@ -54,7 +55,7 @@ struct Plot_Scene::Impl { case Plot_Frame_Mode::Playback: return std::make_unique(); } - throw std::invalid_argument("unknown Plot_Scene frame mode"); + ::renderive::error::unexpected("unknown Plot_Scene frame mode"); } Plot_Frame_Mode mode; diff --git a/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp b/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp index 328dd07..11ff7da 100644 --- a/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp +++ b/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "../Plot_Scene.h" #include "../../render/Blend2D_Cache.h" #include @@ -129,7 +130,7 @@ struct Plot_Scene_Model final : ::Scene2D_Contextset_object_name("root"); auto builder = this->attach_builder(); if (builder.attach(root) != Scene_Edit_Error::none) - throw std::logic_error("validated plot root attachment failed"); + ::renderive::error::unexpected("validated plot root attachment failed"); } [[nodiscard]] renderive_Owner root_renderable() const { const auto topology = this->topology_snapshot(); @@ -208,7 +209,7 @@ struct Plot_Scene_Model final : ::Scene2D_Context("unknown scene render error"); } [[nodiscard]] bool discard_pending_frame() { if constexpr (Mode == Plot_Frame_Mode::Playback) @@ -239,7 +240,8 @@ struct Plot_Scene_Model final : ::Scene2D_Contextnotify_model_dirty(); - throw; + ::renderive::error::unexpected( + "rolling back plot frame preparation", std::current_exception()); } const auto finished = std::chrono::steady_clock::now(); const double duration_ms = std::chrono::duration(finished - started).count(); diff --git a/render_3D/CMakeLists.txt b/render_3D/CMakeLists.txt index e91c178..7a1e26d 100644 --- a/render_3D/CMakeLists.txt +++ b/render_3D/CMakeLists.txt @@ -158,7 +158,9 @@ if (RENDERIVE_BUILD_TESTS) endif () renderive_stage_render_3D_runtime("${Renderive_render_3D_test_target}") add_test(NAME "${Renderive_render_3D_test_target}" COMMAND "${Renderive_render_3D_test_target}") - set_tests_properties("${Renderive_render_3D_test_target}" PROPERTIES LABELS "Renderive_render_3D") + set_tests_properties("${Renderive_render_3D_test_target}" PROPERTIES + LABELS "Renderive_render_3D" + ENVIRONMENT "RENDERIVE_ERROR_MODE=exception") list(APPEND Renderive_render_3D_test_targets "${Renderive_render_3D_test_target}") endforeach () add_custom_target(Renderive_render_3D_check diff --git a/render_3D/render_3D/Point_Scene.cpp b/render_3D/render_3D/Point_Scene.cpp index 79eb6d4..06ec8a7 100644 --- a/render_3D/render_3D/Point_Scene.cpp +++ b/render_3D/render_3D/Point_Scene.cpp @@ -1,3 +1,4 @@ +#include #include "Point_Scene.h" #include "Scene_Context.hpp" @@ -124,7 +125,7 @@ struct Point_Backend_State final { detail::Datoviz_Visual_Backend& require_backend() { if (!backend) - throw std::logic_error("Datoviz backend invariant is unavailable"); + ::renderive::error::unexpected("Datoviz backend invariant is unavailable"); return *backend; } [[nodiscard]] bool backend_available() const noexcept { @@ -176,7 +177,7 @@ struct Basic_Point_Scene final void initialize(const Scene_Options& options, std::shared_ptr point_visual) { if (!point_visual) - throw std::invalid_argument("Point_Scene requires a Point_Visual"); + ::renderive::error::unexpected("Point_Scene requires a Point_Visual"); visual = std::move(point_visual); point_id = visual->renderable_id(); this->Scene_State_Strategy::template set< @@ -189,7 +190,7 @@ struct Basic_Point_Scene final auto builder = this->attach_builder(); if (builder.attach(renderive_Owner(visual)) != Scene_Edit_Error::none) - throw std::logic_error("validated point visual attachment failed"); + ::renderive::error::unexpected("validated point visual attachment failed"); } const detail::Scene_State initial{ options.viewport, options.clear_color, options.visual_family}; @@ -199,7 +200,7 @@ struct Basic_Point_Scene final options.gpu_index, options.validation_enabled, initial); }); if (!invocation) - throw std::logic_error("render domain stopped during Point_Scene initialization"); + ::renderive::error::unexpected("render domain stopped during Point_Scene initialization"); } void resize(Extent extent) override { @@ -320,7 +321,7 @@ struct Basic_Point_Scene final case Scene_Render_Error::shutting_down: return Frame_Request_Error::scene_shutting_down; } - throw std::logic_error("unknown scene render error"); + ::renderive::error::unexpected("unknown scene render error"); } [[nodiscard]] Frame_Request_Error request_frame() override { @@ -406,7 +407,7 @@ struct Basic_Point_Scene final External_Operation_Error::external_failure); const auto prepared = context.prepared(point_id); if (!prepared) - throw std::logic_error("Point_Visual did not publish prepared data"); + ::renderive::error::unexpected("Point_Visual did not publish prepared data"); const detail::Scene_State scene_state = context.frame.scene_state(); const std::uint64_t scene_revision = @@ -432,8 +433,8 @@ struct Basic_Point_Scene final [state, async_frame, metrics, diagnostics, source]() mutable { if (!async_frame->pending) { static_cast(source->fail( - std::make_exception_ptr(std::logic_error( - "GPU completion has no pending frame")))); + ::renderive::error::capture( + "GPU completion has no pending frame"))); return; } auto pending = std::move(*async_frame->pending); @@ -447,7 +448,10 @@ struct Basic_Point_Scene final static_cast(source->fail( External_Operation_Error::external_failure)); } catch (...) { - static_cast(source->fail(std::current_exception())); + static_cast(source->fail( + ::renderive::error::capture( + "quarantining Point_Scene backend", + std::current_exception()))); } return; } @@ -474,7 +478,9 @@ struct Basic_Point_Scene final })); } catch (...) { static_cast(source->fail( - std::current_exception())); + ::renderive::error::capture( + "collecting Point_Scene frame", + std::current_exception()))); } }, [source](std::exception_ptr exception) { static_cast(source->fail(std::move(exception))); @@ -498,7 +504,9 @@ struct Basic_Point_Scene final static_cast(source->cancel()); } catch (...) { static_cast(source->fail( - std::current_exception())); + ::renderive::error::capture( + "posting Point_Scene completion", + std::current_exception()))); } }, [source](std::exception_ptr exception) { @@ -556,8 +564,10 @@ struct Basic_Point_Scene final std::move(*pending)); completion->watch(owned.device, owned.fence); } catch (...) { - static_cast( - source->fail(std::current_exception())); + static_cast(source->fail( + ::renderive::error::capture( + "submitting Point_Scene frame", + std::current_exception()))); } }, [source](std::exception_ptr exception) { static_cast(source->fail(std::move(exception))); @@ -565,7 +575,10 @@ struct Basic_Point_Scene final if (post_error != detail::Render_Domain::Error::none) static_cast(source->cancel()); } catch (...) { - static_cast(source->fail(std::current_exception())); + static_cast(source->fail( + ::renderive::error::capture( + "posting Point_Scene render task", + std::current_exception()))); } return Node_Execution_Result::external(operation); } @@ -591,7 +604,7 @@ std::unique_ptr make_scene_model( Basic_Point_Scene>( options, std::move(visual)); } - throw std::invalid_argument("unknown Point_Scene frame mode"); + ::renderive::error::unexpected("unknown Point_Scene frame mode"); } } // namespace @@ -607,12 +620,12 @@ struct Point_Scene::Impl { Point_Scene::Point_Scene(Scene_Options options, std::shared_ptr visual) { if (!valid(options.viewport)) - throw std::invalid_argument("Point_Scene viewport must be nonempty"); + ::renderive::error::unexpected("Point_Scene viewport must be nonempty"); if (!valid(options.clear_color)) - throw std::invalid_argument("Point_Scene clear color must be in [0, 1]"); + ::renderive::error::unexpected("Point_Scene clear color must be in [0, 1]"); if (!std::isfinite(options.maximum_frames_per_second) || options.maximum_frames_per_second <= 0.0) - throw std::invalid_argument( + ::renderive::error::unexpected( "Point_Scene frame frequency must be positive"); impl_ = std::make_unique(options, std::move(visual)); } diff --git a/render_3D/render_3D/Point_Visual.cpp b/render_3D/render_3D/Point_Visual.cpp index 7b85333..fa23efb 100644 --- a/render_3D/render_3D/Point_Visual.cpp +++ b/render_3D/render_3D/Point_Visual.cpp @@ -1,3 +1,4 @@ +#include #include "Point_Visual.h" #include "detail/Point_Core.h" @@ -49,11 +50,11 @@ struct Point_Data_Observation { void Point_Visual::State_Validator::operator()(const State& state) const { if (!std::isfinite(state.style.stroke_width_px) || state.style.stroke_width_px < 0.0F) - throw std::invalid_argument( + ::renderive::error::unexpected( "point stroke width must be finite and nonnegative"); for (float value : state.transform.values) { if (!std::isfinite(value)) - throw std::invalid_argument( + ::renderive::error::unexpected( "point transform must contain finite values"); } } @@ -73,7 +74,7 @@ struct Point_Visual::Impl : next_Impl, Point_Buffer_Strategy { explicit Impl(std::vector points) { if (update_points(std::move(points)) != Visual_Data_Error::none) - throw std::invalid_argument("point payload contains invalid coordinates or diameter"); + ::renderive::error::unexpected("point payload contains invalid coordinates or diameter"); } std::shared_ptr real_time_data_binding; @@ -124,7 +125,7 @@ struct Point_Visual::Impl : next_Impl, Point_Buffer_Strategy { const auto& scene = context.frame.scene_state(); const auto input = context.captured(); if (!input) - throw std::logic_error("Point_Visual frame input was not captured"); + ::renderive::error::unexpected("Point_Visual frame input was not captured"); auto output = std::make_shared(); output->state = input->state; output->family = scene.visual_family; @@ -134,7 +135,7 @@ struct Point_Visual::Impl : next_Impl, Point_Buffer_Strategy { const auto& source = input->points; const std::size_t count = source ? source->size() : 0U; if (count > std::numeric_limits::max()) - throw std::length_error("Datoviz point payload is too large"); + ::renderive::error::unexpected("Datoviz point payload is too large"); output->positions.reserve(count); output->colors.reserve(count); output->sizes.reserve(count); diff --git a/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp b/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp index 758ebc0..381a354 100644 --- a/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp +++ b/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp @@ -1,3 +1,4 @@ +#include #include "Datoviz_Visual_Backend.h" #include @@ -29,7 +30,7 @@ template Resource* allocate_wrapper(Allocate allocate, const char* message) { Resource* resource = allocate(); if (resource == nullptr) - throw std::runtime_error(message); + ::renderive::error::unexpected(message); return resource; } @@ -110,10 +111,10 @@ void configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) dvz_text_atlas_spec(DVZ_TEXT_RENDERER_MSDF_ATLAS, 64.0F); if (font == nullptr || !dvz_font_atlas_ensure_string(font, &atlas_specification, text)) - throw std::runtime_error("failed to create Datoviz text atlas"); + ::renderive::error::unexpected("failed to create Datoviz text atlas"); const auto* atlas = dvz_font_atlas(font, &atlas_specification); if (atlas == nullptr || dvz_glyph_set_atlas(visual, atlas) != DVZ_OK) - throw std::runtime_error("failed to bind Datoviz text atlas"); + ::renderive::error::unexpected("failed to bind Datoviz text atlas"); float line_width = 0.0F; for (const auto* character = text; *character != '\0'; ++character) { @@ -157,7 +158,7 @@ void configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) ++glyph_index; } if (positions.empty()) - throw std::runtime_error("Datoviz text atlas contains no visible glyphs"); + ::renderive::error::unexpected("Datoviz text atlas contains no visible glyphs"); const auto count = static_cast(positions.size()); const std::array updates{{ {"position", positions.data(), count}, {"bounds", bounds.data(), count}, @@ -166,7 +167,7 @@ void configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) if (dvz_visual_set_data_many(visual, updates.data(), static_cast(updates.size())) != DVZ_OK || dvz_visual_set_depth_test(visual, false) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz text geometry"); + ::renderive::error::unexpected("failed to upload Datoviz text geometry"); } } // namespace @@ -181,18 +182,18 @@ public: Frame_Target(DvzGpuCtx* gpu_context, Extent extent, std::uint64_t generation) : gpu_context_(gpu_context), extent_(extent), generation_(generation) { if (gpu_context == nullptr || extent.empty()) - throw std::invalid_argument("invalid Datoviz point frame target"); + ::renderive::error::unexpected("invalid Datoviz point frame target"); const std::uint64_t byte_size = static_cast(extent.width) * extent.height * 4ULL; if (byte_size > std::numeric_limits::max()) - throw std::length_error("Datoviz point frame target is too large"); + ::renderive::error::unexpected("Datoviz point frame target is too large"); byte_size_ = static_cast(byte_size); DvzDevice* device = dvz_gpu_ctx_device(gpu_context_); DvzVma* allocator = dvz_gpu_ctx_alloc(gpu_context_); DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN); if (device == nullptr || allocator == nullptr || queue == nullptr) - throw std::runtime_error("Datoviz GPU context is incomplete"); + ::renderive::error::unexpected("Datoviz GPU context is incomplete"); try { image_ = allocate_wrapper(dvz_images_create_wrapper, "failed to allocate Datoviz image"); @@ -204,7 +205,7 @@ public: VK_IMAGE_USAGE_TRANSFER_SRC_BIT); dvz_images_alloc_flags(image_, DVZ_ALLOC_FLAGS_NONE); if (dvz_images_create(image_) != 0) - throw std::runtime_error("failed to create Datoviz point image"); + ::renderive::error::unexpected("failed to create Datoviz point image"); view_ = allocate_wrapper(dvz_image_views_create_wrapper, "failed to allocate Datoviz image view"); @@ -214,19 +215,19 @@ public: dvz_image_views_mip(view_, 0, 1); dvz_image_views_layers(view_, 0, 1); if (dvz_image_views_create(view_) != 0) - throw std::runtime_error("failed to create Datoviz point image view"); + ::renderive::error::unexpected("failed to create Datoviz point image view"); commands_ = allocate_wrapper(dvz_commands_create_wrapper, "failed to allocate Datoviz commands"); dvz_commands(device, queue, 1, commands_); if (dvz_commands_handle(commands_) == VK_NULL_HANDLE) - throw std::runtime_error("failed to create Datoviz command buffer"); + ::renderive::error::unexpected("failed to create Datoviz command buffer"); fence_ = allocate_wrapper(dvz_fence_create_wrapper, "failed to allocate Datoviz fence"); dvz_fence(device, true, fence_); if (dvz_fence_handle(fence_) == VK_NULL_HANDLE) - throw std::runtime_error("failed to create Datoviz fence"); + ::renderive::error::unexpected("failed to create Datoviz fence"); submit_ = allocate_wrapper(dvz_submit_create_wrapper, "failed to allocate Datoviz submit"); @@ -237,18 +238,19 @@ public: dvz_buffer_flags(readback_, DVZ_ALLOC_HOST_ACCESS_RANDOM | DVZ_ALLOC_MAPPED); dvz_buffer_usage(readback_, VK_BUFFER_USAGE_TRANSFER_DST_BIT); if (dvz_buffer_create(readback_) != 0) - throw std::runtime_error("failed to create Datoviz readback buffer"); + ::renderive::error::unexpected("failed to create Datoviz readback buffer"); } catch (...) { destroy(); - throw; + ::renderive::error::unexpected( + "creating Datoviz frame target", std::current_exception()); } } - ~Frame_Target() { destroy(); } + ~Frame_Target() noexcept(false) { destroy(); } void begin(bool observe) { if (in_flight_) - throw std::logic_error("Datoviz frame target is still in flight"); + ::renderive::error::unexpected("Datoviz frame target is still in flight"); observing_ = observe; if (observing_ && !timestamps_initialized_) { initialize_timestamps( @@ -257,7 +259,7 @@ public: } dvz_cmd_reset(commands_); if (dvz_cmd_begin_result(commands_) != 0) - throw std::runtime_error("failed to begin Datoviz command buffer"); + ::renderive::error::unexpected("failed to begin Datoviz command buffer"); DvzBarriers barriers{}; dvz_barriers(&barriers); @@ -318,7 +320,7 @@ public: void submit() { if (!recording_) - throw std::logic_error("Datoviz frame target is not recording"); + ::renderive::error::unexpected("Datoviz frame target is not recording"); const VkCommandBuffer command_buffer = dvz_commands_handle(commands_); if (observing_ && timestamps_supported_) { vkCmdWriteTimestamp( @@ -372,7 +374,7 @@ public: } if (dvz_cmd_end_result(commands_) != 0) - throw std::runtime_error("failed to end Datoviz command buffer"); + ::renderive::error::unexpected("failed to end Datoviz command buffer"); recording_ = false; dvz_fence_reset(fence_); dvz_submit(submit_); @@ -380,14 +382,14 @@ public: DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN); if (dvz_submit_send(submit_, dvz_queue_handle(queue), dvz_fence_handle(fence_)) != VK_SUCCESS) - throw std::runtime_error("failed to submit Datoviz point frame"); + ::renderive::error::unexpected("failed to submit Datoviz point frame"); in_flight_ = true; completed_layout_ = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; } [[nodiscard]] Collection collect() { if (!in_flight_) - throw std::logic_error("Datoviz frame target has no pending frame"); + ::renderive::error::unexpected("Datoviz frame target has no pending frame"); Collection result; try { result.pixels.resize(static_cast(byte_size_)); @@ -396,7 +398,8 @@ public: } catch (...) { in_flight_ = false; observing_ = false; - throw; + ::renderive::error::unexpected( + "submitting Datoviz frame target", std::current_exception()); } in_flight_ = false; observing_ = false; @@ -405,7 +408,7 @@ public: void discard_after_completion() { if (!in_flight_) - throw std::logic_error("Datoviz frame target has no pending frame"); + ::renderive::error::unexpected("Datoviz frame target has no pending frame"); in_flight_ = false; observing_ = false; } @@ -513,9 +516,10 @@ private: elapsed(timestamps[0], timestamps[3])}; } - void destroy() noexcept { + void destroy() { if (in_flight_) - std::terminate(); + ::renderive::error::unexpected( + "Datoviz frame target is still in flight during destruction"); if (query_pool_ != VK_NULL_HANDLE && gpu_context_ != nullptr) { vkDestroyQueryPool( dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)), @@ -585,34 +589,35 @@ Datoviz_Visual_Backend::Datoviz_Visual_Backend( dvz_gpu_ctx_config_enable_canvas_extensions(&configuration, false); gpu_context_ = dvz_gpu_ctx(&configuration); if (gpu_context_ == nullptr) - throw std::runtime_error("failed to create Datoviz GPU context"); + ::renderive::error::unexpected("failed to create Datoviz GPU context"); DvzDrp2RuntimeConfig runtime_configuration = dvz_drp2_runtime_vklite_config( dvz_gpu_ctx_device(gpu_context_), dvz_gpu_ctx_alloc(gpu_context_)); runtime_ = dvz_drp2_runtime_vklite(&runtime_configuration); if (runtime_ == nullptr) - throw std::runtime_error("failed to create Datoviz DRP2 runtime"); + ::renderive::error::unexpected("failed to create Datoviz DRP2 runtime"); create_scene(initial_scene); } catch (...) { destroy(); - throw; + ::renderive::error::unexpected( + "creating Datoviz backend", std::current_exception()); } } -Datoviz_Visual_Backend::~Datoviz_Visual_Backend() { destroy(); } +Datoviz_Visual_Backend::~Datoviz_Visual_Backend() noexcept(false) { destroy(); } void Datoviz_Visual_Backend::require_domain() const { if (std::this_thread::get_id() != domain_thread_) - throw std::logic_error("Datoviz objects may only be used on the render domain"); + ::renderive::error::unexpected("Datoviz objects may only be used on the render domain"); } void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { const Visual_Family family = initial_scene.visual_family; scene_ = dvz_scene(); if (scene_ == nullptr) - throw std::runtime_error("failed to create Datoviz scene"); + ::renderive::error::unexpected("failed to create Datoviz scene"); const auto capabilities = dvz_capability_snapshot(); if (dvz_scene_set_capabilities(scene_, &capabilities) != DVZ_OK) - throw std::runtime_error("failed to configure Datoviz scene capabilities"); + ::renderive::error::unexpected("failed to configure Datoviz scene capabilities"); figure_ = dvz_figure(scene_, initial_scene.viewport.width, initial_scene.viewport.height, 0); panel_ = figure_ != nullptr ? dvz_panel_full(figure_) : nullptr; @@ -666,7 +671,7 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { } if (visual_ != nullptr && dvz_visual_set_alpha_mode(visual_, DVZ_ALPHA_OPAQUE) != DVZ_OK) - throw std::runtime_error("failed to configure Datoviz visual alpha mode"); + ::renderive::error::unexpected("failed to configure Datoviz visual alpha mode"); if (visual_ != nullptr && family == Visual_Family::Glyph) configure_glyph_text(scene_, visual_, "GLYPH"); @@ -704,7 +709,7 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { view.rows_per_image = height; if (field == nullptr || dvz_sampled_field_set_data(field, &view) != DVZ_OK || dvz_visual_set_field(visual_, "field", field) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz 2D sampled field"); + ::renderive::error::unexpected("failed to create Datoviz 2D sampled field"); } if (visual_ != nullptr && family == Visual_Family::Labels) { @@ -742,7 +747,7 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { dvz_scale_set_categories(scale, categories.data(), static_cast(categories.size())) != DVZ_OK || dvz_visual_set_scale(visual_, "labels", scale) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz label field"); + ::renderive::error::unexpected("failed to create Datoviz label field"); } if (visual_ != nullptr && family == Visual_Family::Volume) { @@ -777,11 +782,11 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { dvz_visual_set_field(visual_, "field", field) != DVZ_OK || dvz_volume_set_render_mode(visual_, DVZ_VOLUME_RENDER_MIP) != DVZ_OK || dvz_volume_set_step_count(visual_, 48) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz volume field"); + ::renderive::error::unexpected("failed to create Datoviz volume field"); } if (figure_ == nullptr || panel_ == nullptr || visual_ == nullptr || dvz_panel_add_visual(panel_, visual_, nullptr) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz visual family"); + ::renderive::error::unexpected("failed to create Datoviz visual family"); DvzCameraDesc camera = dvz_camera_desc(); camera.view.eye[0] = 0.0F; @@ -793,11 +798,11 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { camera.projection.near_clip = 0.01F; camera.projection.far_clip = 100.0F; if (dvz_panel_set_camera_desc(panel_, &camera) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz point camera"); + ::renderive::error::unexpected("failed to create Datoviz point camera"); DvzController* controller = dvz_arcball(scene_, nullptr); if (controller == nullptr || dvz_panel_bind_controller(panel_, controller, DVZ_DIM_MASK_XYZ) != DVZ_OK) - throw std::runtime_error("failed to bind Datoviz arcball controller"); + ::renderive::error::unexpected("failed to bind Datoviz arcball controller"); input_router_ = dvz_input_router(); gesture_handler_ = input_router_ != nullptr @@ -805,7 +810,7 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { : nullptr; if (input_router_ == nullptr || gesture_handler_ == nullptr || dvz_panel_connect_input(panel_, input_router_) != DVZ_OK) - throw std::runtime_error("failed to connect Datoviz point input"); + ::renderive::error::unexpected("failed to connect Datoviz point input"); DvzInputResizeEvent resize{ initial_scene.viewport.width, initial_scene.viewport.height, @@ -820,7 +825,7 @@ void Datoviz_Visual_Backend::apply( if (scene_revision != applied_scene_revision_) { if (dvz_figure_resize(figure_, scene.viewport.width, scene.viewport.height) != DVZ_OK) - throw std::runtime_error("failed to resize Datoviz point figure"); + ::renderive::error::unexpected("failed to resize Datoviz point figure"); DvzInputResizeEvent resize{ scene.viewport.width, scene.viewport.height, scene.viewport.width, scene.viewport.height, 1.0F, 1.0F}; @@ -843,13 +848,13 @@ void Datoviz_Visual_Backend::apply( style.stroke_width_px = state.style.stroke_width_px; style.aspect = aspect(state.style.aspect); if (dvz_point_set_style(visual_, &style) != DVZ_OK) - throw std::runtime_error("failed to apply Datoviz point style"); + ::renderive::error::unexpected("failed to apply Datoviz point style"); } if (dvz_visual_set_transform(visual_, transform) != DVZ_OK || dvz_visual_set_depth_test(visual_, state.depth_test) != DVZ_OK || dvz_visual_set_visible( visual_, state.visible && !point.positions.empty()) != DVZ_OK) - throw std::runtime_error("failed to apply Datoviz visual state"); + ::renderive::error::unexpected("failed to apply Datoviz visual state"); applied_state_revision_ = point.state_revision; } @@ -857,7 +862,7 @@ void Datoviz_Visual_Backend::apply( return; if (point.positions.empty()) { if (dvz_visual_set_visible(visual_, false) != DVZ_OK) - throw std::runtime_error("failed to hide empty Datoviz point visual"); + ::renderive::error::unexpected("failed to hide empty Datoviz point visual"); applied_data_revision_ = point.data_revision; return; } @@ -958,7 +963,7 @@ void Datoviz_Visual_Backend::apply( } if (result != DVZ_OK || dvz_visual_set_visible(visual_, point.state.visible) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz visual payload"); + ::renderive::error::unexpected("failed to upload Datoviz visual payload"); applied_data_revision_ = point.data_revision; } @@ -1029,7 +1034,7 @@ DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit( if (const char* diagnostic = dvz_diagnostic_report_get(&report, 0)) message += ": " + std::string(diagnostic); } - throw std::runtime_error(message); + ::renderive::error::unexpected(message); } return artifact; } @@ -1067,7 +1072,8 @@ Datoviz_Visual_Backend::submit( trace.emit_ns = trace_now_ns() - phase_started; } catch (...) { target_->abort(); - throw; + ::renderive::error::unexpected( + "preparing Datoviz frame", std::current_exception()); } if (observe) { trace.artifact_status = static_cast( @@ -1102,7 +1108,7 @@ Datoviz_Visual_Backend::submit( dvz_scene_frame_artifact_destroy(artifact); if (!attached) { target_->abort(); - throw std::runtime_error("failed to attach the Datoviz point frame target"); + ::renderive::error::unexpected("failed to attach the Datoviz point frame target"); } if (!result.ok) { target_->abort(); @@ -1115,7 +1121,7 @@ Datoviz_Visual_Backend::submit( trace.artifact_json.substr( 0, std::min(trace.artifact_json.size(), 2048)); } - throw std::runtime_error(std::move(message)); + ::renderive::error::unexpected(std::move(message)); } if (observe) @@ -1133,7 +1139,7 @@ Datoviz_Visual_Backend::Completed_Frame Datoviz_Visual_Backend::collect( require_domain(); if (target_ == nullptr || target_->generation() != pending.target_generation) - throw std::logic_error("Datoviz pending frame target no longer exists"); + ::renderive::error::unexpected("Datoviz pending frame target no longer exists"); const std::uint64_t readback_started = pending.trace.observed ? trace_now_ns() : 0; @@ -1152,13 +1158,14 @@ void Datoviz_Visual_Backend::discard(Pending_Frame pending) { require_domain(); if (target_ == nullptr || target_->generation() != pending.target_generation) - throw std::logic_error("Datoviz pending frame target no longer exists"); + ::renderive::error::unexpected("Datoviz pending frame target no longer exists"); target_->discard_after_completion(); } -void Datoviz_Visual_Backend::destroy() noexcept { +void Datoviz_Visual_Backend::destroy() { if (std::this_thread::get_id() != domain_thread_) - std::terminate(); + ::renderive::error::unexpected( + "Datoviz backend may only be destroyed on the render domain"); if (runtime_ != nullptr) { dvz_drp2_runtime_destroy(runtime_); runtime_ = nullptr; diff --git a/render_3D/render_3D/detail/Datoviz_Visual_Backend.h b/render_3D/render_3D/detail/Datoviz_Visual_Backend.h index d87c090..6fb68ee 100644 --- a/render_3D/render_3D/detail/Datoviz_Visual_Backend.h +++ b/render_3D/render_3D/detail/Datoviz_Visual_Backend.h @@ -37,7 +37,7 @@ public: Datoviz_Visual_Backend(std::uint32_t gpu_index, bool validation_enabled, const Scene_State& initial_scene); - ~Datoviz_Visual_Backend(); + ~Datoviz_Visual_Backend() noexcept(false); Datoviz_Visual_Backend(const Datoviz_Visual_Backend&) = delete; Datoviz_Visual_Backend& operator=(const Datoviz_Visual_Backend&) = delete; @@ -66,7 +66,7 @@ private: void apply(const Scene_State& scene, std::uint64_t scene_revision, const Prepared_Point& point); [[nodiscard]] DvzSceneFrameArtifact* emit(const Scene_State& scene); - void destroy() noexcept; + void destroy(); std::thread::id domain_thread_; DvzGpuCtx* gpu_context_{}; diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp index 4e9679d..f68589d 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp @@ -1,3 +1,4 @@ +#include #include "Gpu_Completion_Service.h" #include #include @@ -27,13 +28,13 @@ Gpu_Completion_Service::Reservation::Reservation(Reservation&& other) noexcept void Gpu_Completion_Service::Reservation::watch(VkDevice device, VkFence fence) { if (!pending_ || device == VK_NULL_HANDLE || fence == VK_NULL_HANDLE) - throw std::logic_error("GPU completion reservation or fence is invalid"); + ::renderive::error::unexpected("GPU completion reservation or fence is invalid"); auto pending = std::exchange(pending_, {}); auto* const service = pending->service; { std::lock_guard lock(pending->mutex); if (pending->status != Pending_Fence::Status::reserved) - throw std::logic_error("GPU completion reservation is not reserved"); + ::renderive::error::unexpected("GPU completion reservation is not reserved"); pending->device = device; pending->fence = fence; // This timestamp is part of correctness, not only observability: it @@ -92,9 +93,9 @@ void Gpu_Completion_Service::release_slot() noexcept { Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare( Completion completion, Exception_Handler on_exception, bool observe) { if (!completion) - throw std::invalid_argument("GPU completion callback is empty"); + ::renderive::error::unexpected("GPU completion callback is empty"); if (!on_exception) - throw std::invalid_argument("GPU completion exception handler is empty"); + ::renderive::error::unexpected("GPU completion exception handler is empty"); if (stopping_.load(std::memory_order_acquire)) return {{}, Error::stopping}; auto pending = std::make_shared(); @@ -109,7 +110,7 @@ Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare( } if (!pending_.try_push(pending)) { release_slot(); - throw std::logic_error("GPU completion admission invariant violated"); + ::renderive::error::unexpected("GPU completion admission invariant violated"); } wake(); return {Reservation(std::move(pending)), Error::none}; @@ -174,7 +175,8 @@ void Gpu_Completion_Service::run() { try { completion(std::move(completion_result)); } catch (...) { - on_exception(std::current_exception()); + on_exception(::renderive::error::capture( + "delivering GPU completion", std::current_exception())); } release_slot(); }; diff --git a/render_3D/render_3D/detail/Render_Domain.cpp b/render_3D/render_3D/detail/Render_Domain.cpp index 9797646..f69b53c 100644 --- a/render_3D/render_3D/detail/Render_Domain.cpp +++ b/render_3D/render_3D/detail/Render_Domain.cpp @@ -1,3 +1,4 @@ +#include #include "Render_Domain.h" #include #include @@ -95,9 +96,9 @@ void Render_Domain::release_admission() noexcept { Render_Domain::Prepare_Result Render_Domain::prepare( std::function function, Exception_Handler on_exception) { if (!function) - throw std::invalid_argument("render domain task is empty"); + ::renderive::error::unexpected("render domain task is empty"); if (!on_exception) - throw std::invalid_argument("render domain exception handler is empty"); + ::renderive::error::unexpected("render domain exception handler is empty"); if (stopping_.load(std::memory_order_acquire)) return {{}, Error::stopping}; acquire_admission(); @@ -110,7 +111,8 @@ Render_Domain::Prepare_Result Render_Domain::prepare( std::move(function), std::move(on_exception))), Error::none}; } catch (...) { release_admission(); - throw; + ::renderive::error::unexpected( + "preparing render domain task", std::current_exception()); } } Render_Domain::Error Render_Domain::post( @@ -122,11 +124,11 @@ Render_Domain::Error Render_Domain::post( } Render_Domain::Error Render_Domain::post(Prepared_Task task) { if (!task.task_ || task.domain_.get() != this) - throw std::logic_error("render domain prepared task is invalid"); + ::renderive::error::unexpected("render domain prepared task is invalid"); if (stopping_.load(std::memory_order_acquire)) return Error::stopping; if (!tasks_.try_push(std::move(task.task_))) - throw std::logic_error("render domain admission invariant violated"); + ::renderive::error::unexpected("render domain admission invariant violated"); const std::size_t queued = queued_.fetch_add(1, std::memory_order_relaxed) + 1; update_peak(peak_queued_, queued); task.domain_.reset(); @@ -158,7 +160,8 @@ void Render_Domain::run() { try { task->function(); } catch (...) { - task->on_exception(std::current_exception()); + task->on_exception(::renderive::error::capture( + "executing render domain task", std::current_exception())); } task.reset(); if (destroy_on_exit_.load(std::memory_order_acquire)) { diff --git a/render_3D/render_3D/detail/Render_Domain.h b/render_3D/render_3D/detail/Render_Domain.h index 4fde745..d9d5f8c 100644 --- a/render_3D/render_3D/detail/Render_Domain.h +++ b/render_3D/render_3D/detail/Render_Domain.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -97,15 +98,22 @@ public: auto result = task->get_future(); output.error = post([task] { (*task)(); }, [](std::exception_ptr exception) { - std::rethrow_exception(exception); + ::renderive::error::unexpected( + "render domain invocation", exception); }); if (output.error != Error::none) return output; - if constexpr (std::is_void_v) { - result.get(); - output.value.emplace(); - } else { - output.value.emplace(result.get()); + try { + if constexpr (std::is_void_v) { + result.get(); + output.value.emplace(); + } else { + output.value.emplace(result.get()); + } + } catch (...) { + ::renderive::error::unexpected( + "collecting render domain invocation", + std::current_exception()); } return output; } diff --git a/render_3D/render_3D/visuals/Basic_Visual.h b/render_3D/render_3D/visuals/Basic_Visual.h index aee51ad..4d9927f 100644 --- a/render_3D/render_3D/visuals/Basic_Visual.h +++ b/render_3D/render_3D/visuals/Basic_Visual.h @@ -1,4 +1,5 @@ #pragma once +#include #include "Visual_Types.h" #include "../renderable/Inheritance.h" @@ -47,13 +48,13 @@ struct Basic_Visual : Renderable> { void operator()(const State& state) const { for (float value : state.transform.values) { if (!finite(value)) - throw std::invalid_argument("visual transform must be finite"); + ::renderive::error::unexpected("visual transform must be finite"); } if constexpr (requires { state.field_width; state.field_height; }) { const bool any = state.field_width || state.field_height; const bool all = state.field_width && state.field_height; if (any && !all) - throw std::invalid_argument( + ::renderive::error::unexpected( "texture field dimensions must be specified together"); } if constexpr (requires { state.field_depth; }) { @@ -62,7 +63,7 @@ struct Basic_Visual : Renderable> { const bool all = state.field_width && state.field_height && state.field_depth; if (any && !all) - throw std::invalid_argument( + ::renderive::error::unexpected( "volume field dimensions must be specified together"); } } @@ -92,7 +93,7 @@ struct Basic_Visual : Renderable> { explicit Impl(std::vector items) { if (update(std::move(items)) != Visual_Data_Error::none) - throw std::invalid_argument(std::string(Spec::name) + + ::renderive::error::unexpected(std::string(Spec::name) + " payload contains invalid data"); } @@ -132,7 +133,7 @@ struct Basic_Visual : Renderable> { void prepare(const Prepare_Render_Context& context) override { const auto input = context.template captured(); if (!input) - throw std::logic_error( + ::renderive::error::unexpected( "Datoviz visual frame input was not captured"); if (context.metrics) { const auto count = input->items ? input->items->size() : 0U; diff --git a/web_server/CMakeLists.txt b/web_server/CMakeLists.txt index f7acce5..2b92687 100644 --- a/web_server/CMakeLists.txt +++ b/web_server/CMakeLists.txt @@ -206,7 +206,9 @@ if (RENDERIVE_BUILD_TESTS) target_link_libraries("${Renderive_Web_test_target}" PRIVATE Renderive_Web Adminive::Nlohmann GTest::gtest_main) renderive_stage_render_3D_runtime("${Renderive_Web_test_target}") add_test(NAME "${Renderive_Web_test_target}" COMMAND "${Renderive_Web_test_target}") - set_tests_properties("${Renderive_Web_test_target}" PROPERTIES LABELS "Renderive_Web") + set_tests_properties("${Renderive_Web_test_target}" PROPERTIES + LABELS "Renderive_Web" + ENVIRONMENT "RENDERIVE_ERROR_MODE=exception") list(APPEND Renderive_Web_test_targets "${Renderive_Web_test_target}") endforeach () add_custom_target(Renderive_Web_check diff --git a/web_server/app/Gallery_Observer_Adminive.h b/web_server/app/Gallery_Observer_Adminive.h index b6d654c..a83321d 100644 --- a/web_server/app/Gallery_Observer_Adminive.h +++ b/web_server/app/Gallery_Observer_Adminive.h @@ -1,4 +1,5 @@ #pragma once +#include #include "Gallery_Enum.h" #include "common/Gallery_Performance_Types.h" @@ -76,7 +77,7 @@ struct Value_Adapter { const auto parsed = renderive::web::gallery_enum_cast(value); if (!parsed) - throw std::invalid_argument("unknown frame control mode: " + value); + ::renderive::error::unexpected("unknown frame control mode: " + value); target = *parsed; } }; diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index 7b818d9..8259e88 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -1,3 +1,4 @@ +#include #include "Gallery_Plot_Session.h" #include "Gallery_Protocol.h" #include "common/Gallery_Scene_Interface.h" @@ -252,7 +253,8 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this(event)) return handle_frame_request(); diff --git a/web_server/app/Gallery_Protocol.cpp b/web_server/app/Gallery_Protocol.cpp index e90a4d4..1ff15ca 100644 --- a/web_server/app/Gallery_Protocol.cpp +++ b/web_server/app/Gallery_Protocol.cpp @@ -1,3 +1,4 @@ +#include #include "Gallery_Protocol.h" #include "Gallery_Actions.h" #include "Gallery_Enum.h" @@ -95,7 +96,7 @@ void append_session_actions(Gallery_Frame_Mode frame_mode, append_gallery_actions>(result); return; } - throw std::invalid_argument("unknown gallery frame mode"); + ::renderive::error::unexpected("unknown gallery frame mode"); } std::size_t session_control_count(std::string_view case_id, Gallery_Frame_Mode frame_mode) { if (case_id.starts_with("datoviz_")) @@ -108,7 +109,7 @@ std::size_t session_control_count(std::string_view case_id, Gallery_Frame_Mode f case Gallery_Frame_Mode::Playback: return gallery_control_count>(); } - throw std::invalid_argument("unknown gallery frame mode"); + ::renderive::error::unexpected("unknown gallery frame mode"); } void append_3d_actions(Gallery_Frame_Mode frame_mode, std::vector& result) { const auto append = [&result](const Gallery_Action_Attribute& action) { @@ -257,7 +258,7 @@ const Json& described_field(std::string_view name) { return field.at("name").template get() == name; }); if (found == fields.end()) - throw std::logic_error("missing Adminive dashboard field: " + std::string(name)); + ::renderive::error::unexpected("missing Adminive dashboard field: " + std::string(name)); return *found; } diff --git a/web_server/app/Gallery_Renderables.h b/web_server/app/Gallery_Renderables.h index 9625423..8768d56 100644 --- a/web_server/app/Gallery_Renderables.h +++ b/web_server/app/Gallery_Renderables.h @@ -1,4 +1,5 @@ #pragma once +#include #include "Gallery_Actions.h" #include "Gallery_Renderable_Types.h" @@ -174,7 +175,7 @@ struct Value_Adapter { !std::all_of(value.begin() + 1, value.end(), [](unsigned char character) { return std::isxdigit(character) != 0; })) - throw std::invalid_argument("color must use #RRGGBB"); + ::renderive::error::unexpected("color must use #RRGGBB"); const auto channel = [&value](std::size_t offset) { return static_cast(std::stoul(value.substr(offset, 2), nullptr, 16)); }; diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index 154c346..2db5e48 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -1,3 +1,4 @@ +#include #include "Gallery_Scene2D.h" #include "../Gallery_Controls.h" #include "../Gallery_Enum.h" @@ -172,7 +173,7 @@ Plot_Frame_Mode plot_frame_mode(Gallery_Frame_Mode mode) { case Gallery_Frame_Mode::Playback: return Plot_Frame_Mode::Playback; } - throw std::invalid_argument("unknown gallery frame mode"); + ::renderive::error::unexpected("unknown gallery frame mode"); } } // namespace class Gallery_Scene final : public Gallery_Scene_Interface { @@ -192,7 +193,7 @@ public: if (attach.attach(group) != Scene_Edit_Error::none || attach.add_display_parent(group, root_) != Scene_Edit_Error::none || attach.add_dependency_parent(group, root_) != Scene_Edit_Error::none) - throw std::logic_error("validated gallery group attachment failed"); + ::renderive::error::unexpected("validated gallery group attachment failed"); return group; }; axes_node_ = make_group("坐标轴层"); @@ -393,7 +394,7 @@ public: if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; - throw std::logic_error("validated capture request rejected"); + ::renderive::error::unexpected("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id); return "Capture next render frame requested"; @@ -411,7 +412,7 @@ public: if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; - throw std::logic_error("validated capture request rejected"); + ::renderive::error::unexpected("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id) + ", frames=" + std::to_string(count); @@ -916,7 +917,7 @@ private: consumer_feedback_source_ = "none"; if (!feedback_policy_.enabled) { if (plot_.clear_consumer_feedback() != Plot_Control_Error::none) - throw std::logic_error("low-latency feedback clearing failed"); + ::renderive::error::unexpected("low-latency feedback clearing failed"); consumer_feedback_source_ = "disabled"; return; } @@ -944,11 +945,11 @@ private: consumer_manual_interval_ns_, "manual"); if (interval_ns == 0) { if (plot_.clear_consumer_feedback() != Plot_Control_Error::none) - throw std::logic_error("low-latency feedback clearing failed"); + ::renderive::error::unexpected("low-latency feedback clearing failed"); return; } if (plot_.set_consumer_feedback({interval_ns}) != Plot_Control_Error::none) - throw std::logic_error("validated low-latency feedback was rejected"); + ::renderive::error::unexpected("validated low-latency feedback was rejected"); } void record_performance(std::chrono::steady_clock::time_point started, bool rendered) { const auto finished = std::chrono::steady_clock::now(); diff --git a/web_server/app/render_3D/Gallery_Scene3D.cpp b/web_server/app/render_3D/Gallery_Scene3D.cpp index 56923ab..5d2c701 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.cpp +++ b/web_server/app/render_3D/Gallery_Scene3D.cpp @@ -1,3 +1,4 @@ +#include #include "Gallery_Scene3D.h" #include "../Gallery_Capture_Json.h" @@ -39,7 +40,7 @@ struct Value_Adapter< const auto parsed = renderive::web::gallery_enum_cast< renderive::render_3d::Renderable_Observer_Event>(value); if (!parsed) - throw std::invalid_argument("unknown 3D observer event"); + ::renderive::error::unexpected("unknown 3D observer event"); target = *parsed; } }; @@ -93,7 +94,7 @@ Gallery_Render_Error gallery_render_error(Scene_Render_Error error) { case Scene_Render_Error::shutting_down: return Gallery_Render_Error::shutting_down; } - throw std::logic_error("unknown scene render error"); + ::renderive::error::unexpected("unknown scene render error"); } Frame_Request_Error frame_request_error(Scene_Render_Error error) { switch (error) { @@ -108,7 +109,7 @@ Frame_Request_Error frame_request_error(Scene_Render_Error error) { case Scene_Render_Error::shutting_down: return Frame_Request_Error::scene_shutting_down; } - throw std::logic_error("unknown scene render error"); + ::renderive::error::unexpected("unknown scene render error"); } struct Gallery_Point { @@ -125,7 +126,7 @@ Frame_Mode render_frame_mode(Gallery_Frame_Mode mode) { case Gallery_Frame_Mode::Playback: return Frame_Mode::Playback; } - throw std::invalid_argument("unknown gallery frame mode"); + ::renderive::error::unexpected("unknown gallery frame mode"); } std::string mode_id(Gallery_Frame_Mode mode) { @@ -231,7 +232,7 @@ public: .visual_family = visual_family(case_id_)}, visual_) { if (!visual_) - throw std::runtime_error("failed to create Point_Visual"); + ::renderive::error::unexpected("failed to create Point_Visual"); visual_->set_object_name("3D Visual"); render_initial_frame(); } @@ -401,7 +402,7 @@ public: if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; - throw std::logic_error("validated capture request rejected"); + ::renderive::error::unexpected("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id); @@ -422,7 +423,7 @@ public: if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; - throw std::logic_error("validated capture request rejected"); + ::renderive::error::unexpected("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id) + @@ -617,7 +618,7 @@ private: points.push_back({{0.0F, 0.62F, 0.0F}, {255, 230, 20, 255}, 58.0F}); if (visual_->update_points(std::move(points)) != Visual_Data_Error::none) - throw std::logic_error("generated gallery point data is invalid"); + ::renderive::error::unexpected("generated gallery point data is invalid"); } void advance_blue_point(float step) { diff --git a/web_server/tests/Web_Bridge_Tests.cpp b/web_server/tests/Web_Bridge_Tests.cpp index 388e5d3..13a9fb6 100644 --- a/web_server/tests/Web_Bridge_Tests.cpp +++ b/web_server/tests/Web_Bridge_Tests.cpp @@ -527,7 +527,11 @@ TEST(RenderiveWebGallery, PerformanceCaptureReturnsPlanFrameAnalysisAndStatistic ASSERT_EQ(rendered.at("type"), "case_state"); } - const auto& capture = rendered.at("telemetry").at("performance_capture"); + nlohmann::json capture; + ASSERT_TRUE(wait_for_condition([&] { + capture = observe_telemetry(session).at("performance_capture"); + return !capture.at("controller").at("enabled").get(); + })); EXPECT_FALSE(capture.at("controller").at("enabled")); ASSERT_EQ(capture.at("sessions").size(), 1U); const auto& captured = capture.at("sessions").at(0);