diff --git a/Kernel/src/renderive/error/Error_Policy.cpp b/Kernel/src/renderive/error/Error_Policy.cpp new file mode 100644 index 0000000..dae2716 --- /dev/null +++ b/Kernel/src/renderive/error/Error_Policy.cpp @@ -0,0 +1,51 @@ +#include "Error_Policy.hpp" +#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; +} +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 { + static const Mode value = read_mode(); + return value; +} +[[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(); +} +[[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..7c9fc0c --- /dev/null +++ b/Kernel/src/renderive/error/Error_Policy.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include +#include +#include +namespace renderive::error { +enum class Mode { + fast_fail, + exception +}; +[[nodiscard]] 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; +[[noreturn]] void unexpected(std::string_view context, std::exception_ptr exception); +template +[[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/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index d1e926a..e4d4732 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -1,4 +1,5 @@ #include "Scene_Base.hpp" +#include "renderive/error/Error_Policy.hpp" #include #include @@ -86,7 +87,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))) - std::terminate(); + renderive::error::unexpected("scene execution queue rejected admitted operation"); schedule(); } void wait() noexcept { @@ -1124,9 +1125,9 @@ const Frame_Control_Strategy_Base& Scene_Base::frame_control_strategy() const { } void Scene_Base::bind_raster_capabilities( - renderive::scene::detail::Raster_Capabilities& capabilities) noexcept { + renderive::scene::detail::Raster_Capabilities& capabilities) { if (raster_capabilities_ != nullptr) - std::terminate(); + renderive::error::unexpected("scene raster capabilities are already bound"); raster_capabilities_ = &capabilities; } diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index cfd274c..ae0740e 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -320,7 +320,7 @@ private: [[nodiscard]] Scene_Edit_Error validate_structure_locked() const; [[nodiscard]] Scene_Edit_Error cleanup_detached_topology_locked(); void bind_raster_capabilities( - renderive::scene::detail::Raster_Capabilities& capabilities) noexcept; + renderive::scene::detail::Raster_Capabilities& capabilities); inline static thread_local Scene_Base* active_execution_scene_{}; inline static thread_local Scene_Base* active_renderable_edit_scene_{}; diff --git a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp index 48aa4a1..210bdd6 100644 --- a/render_3D/render_3D/detail/Gpu_Completion_Service.cpp +++ b/render_3D/render_3D/detail/Gpu_Completion_Service.cpp @@ -1,4 +1,5 @@ #include "Gpu_Completion_Service.h" +#include "renderive/error/Error_Policy.hpp" #include #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,7 +93,7 @@ void Gpu_Completion_Service::release_slot() noexcept { Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare( Completion completion, bool observe) { if (!completion) - throw std::invalid_argument("GPU completion callback is empty"); + renderive::error::unexpected("GPU completion callback is empty"); if (stopping_.load(std::memory_order_acquire)) return {{}, Error::stopping}; auto pending = std::make_shared(); @@ -106,7 +107,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}; diff --git a/render_3D/render_3D/detail/Render_Domain.cpp b/render_3D/render_3D/detail/Render_Domain.cpp index 8327f4d..9df095a 100644 --- a/render_3D/render_3D/detail/Render_Domain.cpp +++ b/render_3D/render_3D/detail/Render_Domain.cpp @@ -1,4 +1,5 @@ #include "Render_Domain.h" +#include "renderive/error/Error_Policy.hpp" #include #include namespace renderive::render_3d::detail { @@ -69,7 +70,7 @@ void Render_Domain::request_stop() noexcept { return; slots_.acquire(); if (!tasks_.try_push(std::unique_ptr{})) - std::terminate(); + renderive::error::fast_fail("render domain stop marker queue rejected admitted task"); } void Render_Domain::update_peak(std::atomic_size_t& peak, std::size_t value) noexcept { std::size_t current = peak.load(std::memory_order_relaxed); @@ -94,7 +95,7 @@ void Render_Domain::release_admission() noexcept { } Render_Domain::Prepare_Result Render_Domain::prepare(std::function function) { if (!function) - throw std::invalid_argument("render domain task is empty"); + renderive::error::unexpected("render domain task is empty"); if (stopping_.load(std::memory_order_acquire)) return {{}, Error::stopping}; acquire_admission(); @@ -117,11 +118,11 @@ Render_Domain::Error Render_Domain::post(std::function function) { } 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();