错误处理

This commit is contained in:
2026-08-16 17:53:46 +08:00
parent 00a3c54820
commit 8612b5ff86
47 changed files with 744 additions and 544 deletions
+48 -25
View File
@@ -96,11 +96,11 @@ struct Scene_Model {
::renderive::Keyboard_Modifier modifiers) = 0;
[[nodiscard]] virtual Scene_Control_Error dispatch_key(
const ::renderive::Key_Event& event) = 0;
[[nodiscard]] virtual bool prepare_frame() = 0;
[[nodiscard]] virtual bool refresh_manual_frame() = 0;
[[nodiscard]] virtual Frame_Request_Error prepare_frame() = 0;
[[nodiscard]] virtual Frame_Request_Error refresh_manual_frame() = 0;
[[nodiscard]] virtual bool discard_pending_frame() = 0;
[[nodiscard]] virtual bool render_prepared_frame() = 0;
[[nodiscard]] virtual bool request_frame() = 0;
[[nodiscard]] virtual Frame_Request_Error render_prepared_frame() = 0;
[[nodiscard]] virtual Frame_Request_Error request_frame() = 0;
[[nodiscard]] virtual std::shared_ptr<const Pixel_Frame> latest_frame()
const = 0;
[[nodiscard]] virtual Frame_Status frame_status() const = 0;
@@ -187,7 +187,9 @@ struct Basic_Point_Scene final
&detail::Scene_State::visual_family>(options.visual_family);
{
auto builder = this->attach_builder();
builder.attach(renderive_Owner<Point_Visual>(visual));
if (builder.attach(renderive_Owner<Point_Visual>(visual)) !=
Scene_Edit_Error::none)
throw std::logic_error("validated point visual attachment failed");
}
const detail::Scene_State initial{
options.viewport, options.clear_color, options.visual_family};
@@ -278,18 +280,21 @@ struct Basic_Point_Scene final
return error;
}
[[nodiscard]] bool prepare_frame() override {
[[nodiscard]] Frame_Request_Error prepare_frame() override {
auto painter = this->frame_control.acquire_painter();
if (!painter)
return false;
return Frame_Request_Error::painter_unavailable;
this->publish_frame_state(*painter);
return true;
return Frame_Request_Error::none;
}
[[nodiscard]] bool refresh_manual_frame() override {
if constexpr (Mode == Frame_Mode::Manual)
return this->frame_control.refresh();
return false;
[[nodiscard]] Frame_Request_Error refresh_manual_frame() override {
if constexpr (Mode == Frame_Mode::Manual) {
return this->frame_control.refresh() == Manual_Refresh_Error::none
? Frame_Request_Error::none
: Frame_Request_Error::no_pending_frame;
}
return Frame_Request_Error::manual_refresh_not_supported;
}
[[nodiscard]] bool discard_pending_frame() override {
@@ -299,19 +304,31 @@ struct Basic_Point_Scene final
return this->frame_control.discard_pending_frame();
}
[[nodiscard]] bool render_prepared_frame() override {
[[nodiscard]] Frame_Request_Error render_prepared_frame() override {
auto renderer = this->frame_control.acquire_renderer();
if (!renderer)
return false;
return Scene_Base::render(*renderer) == Scene_Render_Error::none;
return Frame_Request_Error::renderer_unavailable;
switch (Scene_Base::render(*renderer)) {
case Scene_Render_Error::none:
return Frame_Request_Error::none;
case Scene_Render_Error::cancelled:
return Frame_Request_Error::cancelled;
case Scene_Render_Error::deadline_exceeded:
return Frame_Request_Error::deadline_exceeded;
case Scene_Render_Error::external_failure:
return Frame_Request_Error::external_failure;
case Scene_Render_Error::shutting_down:
return Frame_Request_Error::scene_shutting_down;
}
throw std::logic_error("unknown scene render error");
}
[[nodiscard]] bool request_frame() override {
if (!prepare_frame())
return false;
[[nodiscard]] Frame_Request_Error request_frame() override {
if (const auto error = prepare_frame(); error != Frame_Request_Error::none)
return error;
if constexpr (Mode == Frame_Mode::Manual) {
if (!refresh_manual_frame())
return false;
if (const auto error = refresh_manual_frame(); error != Frame_Request_Error::none)
return error;
}
return render_prepared_frame();
}
@@ -372,7 +389,6 @@ struct Basic_Point_Scene final
{completion.capacity, completion.in_flight, completion.peak_in_flight,
completion.watched, completion.peak_watched, completion.backpressure_count,
completion.backpressure_wait_ns},
render.unhandled_exception_count,
completion.fault_count,
completion.abandoned_count
};
@@ -460,6 +476,8 @@ struct Basic_Point_Scene final
static_cast<void>(source->fail(
std::current_exception()));
}
}, [source](std::exception_ptr exception) {
static_cast<void>(source->fail(std::move(exception)));
});
if (!prepared_completion) {
static_cast<void>(source->cancel());
@@ -483,6 +501,9 @@ struct Basic_Point_Scene final
std::current_exception()));
}
},
[source](std::exception_ptr exception) {
static_cast<void>(source->fail(std::move(exception)));
},
observe);
if (!prepared_gpu_completion) {
static_cast<void>(source->cancel());
@@ -538,6 +559,8 @@ struct Basic_Point_Scene final
static_cast<void>(
source->fail(std::current_exception()));
}
}, [source](std::exception_ptr exception) {
static_cast<void>(source->fail(std::move(exception)));
});
if (post_error != detail::Render_Domain::Error::none)
static_cast<void>(source->cancel());
@@ -643,17 +666,17 @@ Scene_Control_Error Point_Scene::dispatch(const ::renderive::Key_Event& event) {
return impl_->model->dispatch_key(event);
}
bool Point_Scene::prepare_frame() { return impl_->model->prepare_frame(); }
bool Point_Scene::refresh_manual_frame() {
Frame_Request_Error Point_Scene::prepare_frame() { return impl_->model->prepare_frame(); }
Frame_Request_Error Point_Scene::refresh_manual_frame() {
return impl_->model->refresh_manual_frame();
}
bool Point_Scene::discard_pending_frame() {
return impl_->model->discard_pending_frame();
}
bool Point_Scene::render_prepared_frame() {
Frame_Request_Error Point_Scene::render_prepared_frame() {
return impl_->model->render_prepared_frame();
}
bool Point_Scene::request_frame() { return impl_->model->request_frame(); }
Frame_Request_Error Point_Scene::request_frame() { return impl_->model->request_frame(); }
std::shared_ptr<const Pixel_Frame> Point_Scene::latest_frame() const {
return impl_->model->latest_frame();
+15 -5
View File
@@ -34,6 +34,17 @@ enum class Scene_Control_Error : std::uint8_t {
invalid_event,
backend_unavailable
};
enum class Frame_Request_Error : std::uint8_t {
none,
painter_unavailable,
manual_refresh_not_supported,
no_pending_frame,
renderer_unavailable,
cancelled,
deadline_exceeded,
external_failure,
scene_shutting_down
};
enum class Frame_Mode : std::uint8_t {
Manual,
@@ -88,7 +99,6 @@ struct Runtime_Admission_Statistics {
struct Runtime_Statistics {
Runtime_Admission_Statistics render_domain;
Runtime_Admission_Statistics gpu_completion;
std::uint64_t render_domain_unhandled_exception_count{};
std::uint64_t gpu_completion_fault_count{};
std::uint64_t gpu_completion_abandoned_count{};
};
@@ -142,11 +152,11 @@ public:
[[nodiscard]] Scene_Control_Error dispatch(const ::renderive::Key_Event& event);
// Frame strategy operations. request_frame() is the complete low-latency path.
[[nodiscard]] bool prepare_frame();
[[nodiscard]] bool refresh_manual_frame();
[[nodiscard]] Frame_Request_Error prepare_frame();
[[nodiscard]] Frame_Request_Error refresh_manual_frame();
[[nodiscard]] bool discard_pending_frame();
[[nodiscard]] bool render_prepared_frame();
[[nodiscard]] bool request_frame();
[[nodiscard]] Frame_Request_Error render_prepared_frame();
[[nodiscard]] Frame_Request_Error request_frame();
[[nodiscard]] std::shared_ptr<const Pixel_Frame> latest_frame() const;
[[nodiscard]] Frame_Status frame_status() const;
@@ -1,5 +1,4 @@
#include "Gpu_Completion_Service.h"
#include "renderive/error/Error_Policy.hpp"
#include <algorithm>
#include <stdexcept>
#include <utility>
@@ -28,13 +27,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)
renderive::error::unexpected<std::logic_error>("GPU completion reservation or fence is invalid");
throw std::logic_error("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)
renderive::error::unexpected<std::logic_error>("GPU completion reservation is not reserved");
throw std::logic_error("GPU completion reservation is not reserved");
pending->device = device;
pending->fence = fence;
// This timestamp is part of correctness, not only observability: it
@@ -91,13 +90,16 @@ void Gpu_Completion_Service::release_slot() noexcept {
slots_.release();
}
Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
Completion completion, bool observe) {
Completion completion, Exception_Handler on_exception, bool observe) {
if (!completion)
renderive::error::unexpected<std::invalid_argument>("GPU completion callback is empty");
throw std::invalid_argument("GPU completion callback is empty");
if (!on_exception)
throw std::invalid_argument("GPU completion exception handler is empty");
if (stopping_.load(std::memory_order_acquire))
return {{}, Error::stopping};
auto pending = std::make_shared<Pending_Fence>();
pending->completion = std::move(completion);
pending->on_exception = std::move(on_exception);
pending->observe = observe;
pending->service = this;
acquire_slot();
@@ -107,7 +109,7 @@ Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
}
if (!pending_.try_push(pending)) {
release_slot();
renderive::error::unexpected<std::logic_error>("GPU completion admission invariant violated");
throw std::logic_error("GPU completion admission invariant violated");
}
wake();
return {Reservation(std::move(pending)), Error::none};
@@ -129,7 +131,7 @@ void Gpu_Completion_Service::wake() noexcept {
wake_generation_.fetch_add(1, std::memory_order_release);
wake_condition_.notify_one();
}
void Gpu_Completion_Service::run() noexcept {
void Gpu_Completion_Service::run() {
struct Device_Fences {
VkDevice device{VK_NULL_HANDLE};
std::vector<VkFence> fences;
@@ -148,11 +150,13 @@ void Gpu_Completion_Service::run() noexcept {
const std::shared_ptr<Pending_Fence>& pending,
VkResult result, Completion_Error error) {
Completion completion;
Exception_Handler on_exception;
std::chrono::steady_clock::time_point watched_at{};
bool observe{};
{
std::lock_guard lock(pending->mutex);
completion = std::move(pending->completion);
on_exception = std::move(pending->on_exception);
watched_at = pending->watched_at;
observe = pending->observe;
pending->status = Pending_Fence::Status::canceled;
@@ -170,6 +174,7 @@ void Gpu_Completion_Service::run() noexcept {
try {
completion(std::move(completion_result));
} catch (...) {
on_exception(std::current_exception());
}
release_slot();
};
@@ -6,6 +6,7 @@
#include <cstddef>
#include <cstdint>
#include <functional>
#include <exception>
#include <memory>
#include <mutex>
#include <semaphore>
@@ -41,6 +42,7 @@ public:
std::uint64_t abandoned_count{};
};
using Completion = std::function<void(Result)>;
using Exception_Handler = std::function<void(std::exception_ptr)>;
class Reservation final {
public:
Reservation() = default;
@@ -66,7 +68,9 @@ public:
static Gpu_Completion_Service& instance();
Gpu_Completion_Service(const Gpu_Completion_Service&) = delete;
Gpu_Completion_Service& operator=(const Gpu_Completion_Service&) = delete;
[[nodiscard]] Prepare_Result prepare(Completion completion, bool observe);
[[nodiscard]] Prepare_Result prepare(Completion completion,
Exception_Handler on_exception,
bool observe);
[[nodiscard]] Statistics statistics() const noexcept;
private:
struct Pending_Fence {
@@ -79,6 +83,7 @@ private:
VkDevice device{VK_NULL_HANDLE};
VkFence fence{VK_NULL_HANDLE};
Completion completion;
Exception_Handler on_exception;
std::chrono::steady_clock::time_point watched_at{};
Gpu_Completion_Service* service{};
Status status{Status::reserved};
@@ -91,7 +96,7 @@ private:
void acquire_slot();
void release_slot() noexcept;
void wake() noexcept;
void run() noexcept;
void run();
static constexpr std::ptrdiff_t default_capacity = 1024;
static constexpr std::uint64_t fence_wait_timeout_ns = 1'000'000;
static constexpr std::uint64_t maximum_fence_age_ns = 30'000'000'000ULL;
+16 -13
View File
@@ -1,6 +1,6 @@
#include "Render_Domain.h"
#include "renderive/error/Error_Policy.hpp"
#include <mutex>
#include <stdexcept>
#include <unordered_map>
namespace renderive::render_3d::detail {
namespace {
@@ -69,8 +69,7 @@ void Render_Domain::request_stop() noexcept {
expected, true, std::memory_order_acq_rel))
return;
slots_.acquire();
if (!tasks_.try_push(std::unique_ptr<Task>{}))
renderive::error::fast_fail("render domain stop marker queue rejected admitted task");
tasks_.push(std::unique_ptr<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);
@@ -93,9 +92,12 @@ void Render_Domain::release_admission() noexcept {
admitted_.fetch_sub(1, std::memory_order_relaxed);
slots_.release();
}
Render_Domain::Prepare_Result Render_Domain::prepare(std::function<void()> function) {
Render_Domain::Prepare_Result Render_Domain::prepare(
std::function<void()> function, Exception_Handler on_exception) {
if (!function)
renderive::error::unexpected<std::invalid_argument>("render domain task is empty");
throw std::invalid_argument("render domain task is empty");
if (!on_exception)
throw std::invalid_argument("render domain exception handler is empty");
if (stopping_.load(std::memory_order_acquire))
return {{}, Error::stopping};
acquire_admission();
@@ -104,25 +106,27 @@ Render_Domain::Prepare_Result Render_Domain::prepare(std::function<void()> funct
return {{}, Error::stopping};
}
try {
return {Prepared_Task(shared_from_this(), std::make_unique<Task>(std::move(function))), Error::none};
return {Prepared_Task(shared_from_this(), std::make_unique<Task>(
std::move(function), std::move(on_exception))), Error::none};
} catch (...) {
release_admission();
throw;
}
}
Render_Domain::Error Render_Domain::post(std::function<void()> function) {
auto prepared = prepare(std::move(function));
Render_Domain::Error Render_Domain::post(
std::function<void()> function, Exception_Handler on_exception) {
auto prepared = prepare(std::move(function), std::move(on_exception));
if (!prepared)
return prepared.error;
return post(std::move(prepared.task));
}
Render_Domain::Error Render_Domain::post(Prepared_Task task) {
if (!task.task_ || task.domain_.get() != this)
renderive::error::unexpected<std::logic_error>("render domain prepared task is invalid");
throw std::logic_error("render domain prepared task is invalid");
if (stopping_.load(std::memory_order_acquire))
return Error::stopping;
if (!tasks_.try_push(std::move(task.task_)))
renderive::error::unexpected<std::logic_error>("render domain admission invariant violated");
throw std::logic_error("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();
@@ -136,8 +140,7 @@ Render_Domain::Statistics Render_Domain::statistics() const noexcept {
queued_.load(std::memory_order_relaxed),
peak_queued_.load(std::memory_order_relaxed),
backpressure_count_.load(std::memory_order_relaxed),
backpressure_wait_ns_.load(std::memory_order_relaxed),
unhandled_exception_count_.load(std::memory_order_relaxed)
backpressure_wait_ns_.load(std::memory_order_relaxed)
};
}
void Render_Domain::run() {
@@ -155,7 +158,7 @@ void Render_Domain::run() {
try {
task->function();
} catch (...) {
unhandled_exception_count_.fetch_add(1, std::memory_order_relaxed);
task->on_exception(std::current_exception());
}
task.reset();
if (destroy_on_exit_.load(std::memory_order_acquire)) {
+14 -6
View File
@@ -17,8 +17,12 @@
namespace renderive::render_3d::detail {
class Render_Domain final : public std::enable_shared_from_this<Render_Domain> {
struct Task {
explicit Task(std::function<void()> value) : function(std::move(value)) {}
Task(std::function<void()> value,
std::function<void(std::exception_ptr)> exception_handler)
: function(std::move(value)),
on_exception(std::move(exception_handler)) {}
std::function<void()> function;
std::function<void(std::exception_ptr)> on_exception;
};
public:
enum class Error : std::uint8_t {
@@ -33,7 +37,6 @@ public:
std::size_t peak_queued{};
std::uint64_t backpressure_count{};
std::uint64_t backpressure_wait_ns{};
std::uint64_t unhandled_exception_count{};
};
class Prepared_Task final {
public:
@@ -62,8 +65,11 @@ public:
~Render_Domain();
Render_Domain(const Render_Domain&) = delete;
Render_Domain& operator=(const Render_Domain&) = delete;
[[nodiscard]] Prepare_Result prepare(std::function<void()> function);
[[nodiscard]] Error post(std::function<void()> function);
using Exception_Handler = std::function<void(std::exception_ptr)>;
[[nodiscard]] Prepare_Result prepare(std::function<void()> function,
Exception_Handler on_exception);
[[nodiscard]] Error post(std::function<void()> function,
Exception_Handler on_exception);
[[nodiscard]] Error post(Prepared_Task task);
template <class Result>
struct Invoke_Result {
@@ -89,7 +95,10 @@ public:
}
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Function>(function));
auto result = task->get_future();
output.error = post([task] { (*task)(); });
output.error = post([task] { (*task)(); },
[](std::exception_ptr exception) {
std::rethrow_exception(exception);
});
if (output.error != Error::none)
return output;
if constexpr (std::is_void_v<Result>) {
@@ -119,7 +128,6 @@ private:
std::atomic_size_t peak_queued_{};
std::atomic_uint64_t backpressure_count_{};
std::atomic_uint64_t backpressure_wait_ns_{};
std::atomic_uint64_t unhandled_exception_count_{};
std::atomic_bool stopping_{};
std::atomic_bool destroy_on_exit_{};
std::thread thread_;
@@ -15,16 +15,23 @@ TEST(GpuCompletionService, AbandonedReservationDoesNotComplete) {
auto prepared = Gpu_Completion_Service::instance().prepare(
[&](Gpu_Completion_Service::Result) {
completion_count.fetch_add(1, std::memory_order_relaxed);
}, false);
}, [](std::exception_ptr) {}, false);
ASSERT_TRUE(prepared);
}
EXPECT_EQ(completion_count.load(std::memory_order_relaxed), 0);
}
TEST(GpuCompletionService, CanceledReservationWakesIdleService) {
auto& service = Gpu_Completion_Service::instance();
const auto previous_deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
while (service.statistics().in_flight != 0 &&
std::chrono::steady_clock::now() < previous_deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
const auto baseline = service.statistics().in_flight;
ASSERT_EQ(baseline, 0U);
{
auto prepared = service.prepare([](Gpu_Completion_Service::Result) {}, false);
auto prepared = service.prepare([](Gpu_Completion_Service::Result) {},
[](std::exception_ptr) {}, false);
ASSERT_TRUE(prepared);
EXPECT_EQ(service.statistics().in_flight, baseline + 1);
}
@@ -59,7 +59,9 @@ std::size_t colored_pixel_count(const Pixel_Frame& frame) {
TEST(PointRenderIntegration, RendersRealRgbaAndResizes) {
try {
auto demo = make_test_scene(Scene_Options{.viewport = {320, 200}});
ASSERT_TRUE(demo.scene->request_frame());
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
Scene_Render_Error::none);
auto first = demo.scene->latest_frame();
ASSERT_NE(first, nullptr);
EXPECT_EQ(first->extent, (Extent{320, 200}));
@@ -67,7 +69,9 @@ TEST(PointRenderIntegration, RendersRealRgbaAndResizes) {
EXPECT_GT(colored_pixel_count(*first), 100U);
demo.scene->resize({480, 270});
ASSERT_TRUE(demo.scene->request_frame());
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
Scene_Render_Error::none);
auto resized = demo.scene->latest_frame();
ASSERT_NE(resized, nullptr);
EXPECT_EQ(resized->extent, (Extent{480, 270}));
@@ -81,7 +85,9 @@ TEST(PointRenderIntegration, RendersRealRgbaAndResizes) {
TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) {
try {
auto demo = make_test_scene(Scene_Options{.viewport = {320, 200}});
ASSERT_TRUE(demo.scene->request_frame());
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
Scene_Render_Error::none);
const auto before = demo.scene->latest_frame()->rgba8;
::renderive::Basic_Pointer_Event<Event_Point> press(
@@ -102,7 +108,9 @@ TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) {
release.button = ::renderive::Mouse_Button::Left;
demo.scene->dispatch(release);
ASSERT_TRUE(demo.scene->request_frame());
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
Scene_Render_Error::none);
const auto after_drag = demo.scene->latest_frame();
ASSERT_NE(after_drag, nullptr);
EXPECT_NE(after_drag->rgba8, before);
@@ -113,7 +121,9 @@ TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) {
wheel.angle_delta_y = -216.0;
demo.scene->dispatch(wheel);
ASSERT_TRUE(demo.scene->request_frame());
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
ASSERT_EQ(demo.scene->render_scene().wait_for_render(),
Scene_Render_Error::none);
const auto after_wheel = demo.scene->latest_frame();
ASSERT_NE(after_wheel, nullptr);
EXPECT_GT(colored_pixel_count(*after_wheel), 100U);
@@ -131,7 +141,8 @@ TEST(PointRenderIntegration,
const auto request = kernel_scene.capture_next_frame();
ASSERT_TRUE(request);
const Capture_Session_Id capture = request.session_id;
ASSERT_TRUE(demo.scene->request_frame());
ASSERT_EQ(demo.scene->request_frame(), Frame_Request_Error::none);
ASSERT_EQ(kernel_scene.wait_for_render(), Scene_Render_Error::none);
const auto session = kernel_scene.capture_session(capture);
ASSERT_TRUE(session);
+7 -4
View File
@@ -28,7 +28,9 @@ struct Test_Scene final
Extent{320, 180});
point_id = visual->renderable_id();
auto builder = attach_builder();
builder.attach(renderive_Owner<Point_Visual>(visual));
if (builder.attach(renderive_Owner<Point_Visual>(visual)) !=
Scene_Edit_Error::none)
throw std::logic_error("test visual attachment failed");
}
~Test_Scene() override { shutdown(); }
@@ -41,7 +43,8 @@ struct Test_Scene final
}
auto renderer = frame_control.acquire_renderer();
ASSERT_TRUE(renderer);
Scene_Base::render(*renderer);
ASSERT_EQ(Scene_Base::render(*renderer), Scene_Render_Error::none);
ASSERT_EQ(wait_for_render(), Scene_Render_Error::none);
}
Node_Execution_Result render_scene(
@@ -127,8 +130,8 @@ TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) {
invalid.style.stroke_width_px = -1.0F;
EXPECT_THROW(visual->set<&Point_State::style>(invalid.style),
std::invalid_argument);
EXPECT_THROW(visual->update_points(std::vector<Point>{{{}, {}, 0.0F}}),
std::invalid_argument);
EXPECT_EQ(visual->update_points(std::vector<Point>{{{}, {}, 0.0F}}),
Visual_Data_Error::invalid_data);
}
TEST(PointRenderPlan, OrdersParallelPreparationBeforeSingleSceneRenderNode) {
+14 -8
View File
@@ -20,7 +20,7 @@ TEST(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
std::atomic<bool> executed{};
auto prepared = domain->prepare([&] {
executed.store(true, std::memory_order_release);
});
}, [](std::exception_ptr) {});
ASSERT_TRUE(prepared);
EXPECT_EQ(domain->post(std::move(prepared.task)), Render_Domain::Error::none);
EXPECT_TRUE(domain->invoke([] {}));
@@ -29,7 +29,7 @@ TEST(RenderDomain, PreparedTaskRunsAfterNoThrowHandoff) {
TEST(RenderDomain, AbandonedPreparedTasksReleaseReservedCapacity) {
auto domain = Render_Domain::acquire(0x7ffffff9U);
for (std::size_t index = 0; index < 128; ++index) {
auto prepared = domain->prepare([] {});
auto prepared = domain->prepare([] {}, [](std::exception_ptr) {});
ASSERT_TRUE(prepared);
}
EXPECT_TRUE(domain->invoke([] {}));
@@ -52,14 +52,20 @@ TEST(RenderDomain, ReportsBoundedAdmissionStatistics) {
EXPECT_TRUE(statistics.admitted <= statistics.capacity);
EXPECT_TRUE(statistics.queued <= statistics.capacity);
}
TEST(RenderDomain, ContainsUnhandledFireAndForgetExceptionsAndKeepsRunning) {
TEST(RenderDomain, DeliversFireAndForgetExceptionsAndKeepsRunning) {
auto domain = Render_Domain::acquire(0x7ffffff6U);
const auto before = domain->statistics().unhandled_exception_count;
EXPECT_EQ(domain->post([] { throw std::runtime_error("unexpected render-domain failure"); }),
std::promise<std::exception_ptr> delivered;
auto exception = delivered.get_future();
EXPECT_EQ(domain->post(
[] { throw std::runtime_error("unexpected render-domain failure"); },
[&delivered](std::exception_ptr value) {
delivered.set_value(std::move(value));
}),
Render_Domain::Error::none);
ASSERT_EQ(exception.wait_for(std::chrono::seconds(1)),
std::future_status::ready);
EXPECT_THROW(std::rethrow_exception(exception.get()), std::runtime_error);
EXPECT_TRUE(domain->invoke([] {}));
const auto after = domain->statistics().unhandled_exception_count;
EXPECT_EQ(after, before + 1U);
const auto value = domain->invoke([] { return 17; });
ASSERT_TRUE(value);
EXPECT_EQ(*value.value, 17);
@@ -73,7 +79,7 @@ TEST(RenderDomain, FinalOwnerMayBeReleasedOnAffinityThread) {
EXPECT_EQ(domain->post([owned = domain, &released]() mutable {
owned.reset();
released.set_value();
}), Render_Domain::Error::none);
}, [](std::exception_ptr) {}), Render_Domain::Error::none);
domain.reset();
EXPECT_EQ(finished.wait_for(std::chrono::seconds(1)),
std::future_status::ready);