错误处理

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
@@ -1,51 +0,0 @@
#include "Error_Policy.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
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<int>(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<int>(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);
}
}
@@ -1,27 +0,0 @@
#pragma once
#include <exception>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
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 <class Exception = std::runtime_error, class... Arguments>
[[noreturn]] void unexpected(Arguments&&... arguments) {
if (mode() == Mode::exception)
throw Exception(std::forward<Arguments>(arguments)...);
try {
Exception exception(std::forward<Arguments>(arguments)...);
fast_fail(exception.what());
} catch (...) {
fast_fail("Renderive unexpected error");
}
}
}
@@ -3,5 +3,5 @@
#include "Frame_Control_Strategy.hpp"
template <class That>
concept Manual_Frame_Refresh_Strategy = Frame_Control_Strategy<That> && requires(That& strategy) {
{ strategy.refresh() } -> std::same_as<bool>;
{ strategy.refresh() } -> std::same_as<typename That::Refresh_Error>;
};
@@ -6,9 +6,14 @@
#include "renderive/base/observer/Observer.hpp"
#include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp"
#include "renderive/real_time_data/Observation.hpp"
enum class Manual_Refresh_Error : std::uint8_t {
none,
no_pending_frame
};
template <class Scene_Frame, Mutex_Type Mutex = std::mutex, class Observer = Observer_State<>>
class Manual_Refresh_Strategy : public Frame_Control_Strategy_Base {
public:
using Refresh_Error = Manual_Refresh_Error;
enum class Observation_Event {
prepared,
prepared_replaced,
@@ -96,7 +101,7 @@ public:
Painter_Lease acquire_painter();
Render_Lease acquire_renderer();
void swap() override;
bool refresh();
[[nodiscard]] Manual_Refresh_Error refresh();
bool discard_pending_frame();
State state() const;
void on_real_time_data_update(const Real_Time_Data_Observation& observation) override;
@@ -140,7 +140,7 @@ void Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::swap() {
publish_frame_control_state(invalid_frequency_hz(), 0);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
bool Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::refresh() {
Manual_Refresh_Error Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::refresh() {
std::unique_lock<Mutex> render_lock(render_mutex_);
Observation observation;
bool refreshed{};
@@ -161,7 +161,8 @@ bool Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::refresh() {
}
render_lock.unlock();
observe(observation);
return refreshed;
return refreshed ? Manual_Refresh_Error::none
: Manual_Refresh_Error::no_pending_frame;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
bool Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::discard_pending_frame() {
@@ -45,15 +45,6 @@ public:
}
entry_->binding_count = 0;
entry_->active.store(false, std::memory_order_release);
try {
auto entries = std::make_shared<Entry_List>();
entries->reserve(state_->entries->size());
for (const auto& value : *state_->entries) {
if (value->active.load(std::memory_order_acquire))
entries->push_back(value);
}
state_->entries = std::move(entries);
} catch (...) {}
}
private:
@@ -21,6 +21,8 @@ public:
Ticket ticket{};
{
std::lock_guard lock(mutex_);
if (failure_)
std::rethrow_exception(failure_);
ticket = ++sequence_;
auto iterator = entries_.emplace(deadline, Entry{ticket, std::move(callback)});
tickets_.emplace(ticket, iterator);
@@ -34,6 +36,8 @@ public:
bool notify{};
{
std::lock_guard lock(mutex_);
if (failure_)
std::rethrow_exception(failure_);
const auto found = tickets_.find(ticket);
if (found == tickets_.end())
return;
@@ -60,7 +64,7 @@ private:
if (thread_.joinable())
thread_.join();
}
void run() noexcept {
void run() {
try {
std::unique_lock lock(mutex_);
for (;;) {
@@ -90,13 +94,13 @@ private:
tickets_.erase(current->second.ticket);
entries_.erase(current);
lock.unlock();
try {
callback();
} catch (...) {
}
callback();
lock.lock();
}
} catch (...) {
std::lock_guard lock(mutex_);
failure_ = std::current_exception();
stopping_ = true;
}
}
std::mutex mutex_;
@@ -105,6 +109,7 @@ private:
std::unordered_map<Ticket, Entries::iterator> tickets_;
Ticket sequence_{};
bool stopping_{};
std::exception_ptr failure_;
std::thread thread_;
};
External_Operation_Error checked_cancellation_error(External_Operation_Error error) {
@@ -149,10 +154,7 @@ struct External_Operation::State {
if (deadline_ticket != 0)
Deadline_Service::instance().cancel(deadline_ticket);
if (callback) {
try {
callback(std::move(result));
} catch (...) {
}
callback(std::move(result));
}
return true;
}
@@ -188,10 +190,7 @@ void External_Operation::on_complete(Completion completion) const {
invoke = true;
}
if (invoke) {
try {
completion(std::move(result));
} catch (...) {
}
completion(std::move(result));
}
}
bool External_Operation::cancel(External_Operation_Error error) const {
@@ -104,8 +104,9 @@ struct Render_Graph_Runtime::State
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
arena.execute([this] {
for (auto& node : nodes) {
if (node.root)
node.ready->try_put(Message{});
if (node.root && !node.ready->try_put(Message{}))
throw std::logic_error(
"render graph rejected root execution message");
}
graph.wait_for_all();
});
@@ -163,7 +164,7 @@ struct Render_Graph_Runtime::State
}
}
void run_node(std::size_t index,
Execute_Node_Type::gateway_type& gateway) noexcept {
Execute_Node_Type::gateway_type& gateway) {
try {
if (failed.load(std::memory_order_acquire))
return;
@@ -199,7 +200,7 @@ struct Render_Graph_Runtime::State
}
if (!result.is_external()) {
complete(index, cpu_end);
gateway.try_put(Message{});
static_cast<void>(gateway.try_put(Message{}));
return;
}
if (auto* execution = executions[index]) {
@@ -222,15 +223,19 @@ struct Render_Graph_Runtime::State
[self = std::move(self), gateway_ptr, index](
External_Operation_Completion completion) {
const std::uint64_t end = render_clock_now_ns();
self->clear_external(index);
if (completion.exception) {
self->fail_exception(index,
std::move(completion.exception), end);
} else if (completion.error != External_Operation_Error::none) {
self->fail_expected(index, completion.error, end);
} else {
self->complete_external(index, end);
gateway_ptr->try_put(Message{});
try {
self->clear_external(index);
if (completion.exception) {
self->fail_exception(
index, std::move(completion.exception), end);
} else if (completion.error != External_Operation_Error::none) {
self->fail_expected(index, completion.error, end);
} else {
self->complete_external(index, end);
static_cast<void>(gateway_ptr->try_put(Message{}));
}
} catch (...) {
self->fail_exception(index, std::current_exception(), end);
}
gateway_ptr->release_wait();
});
@@ -288,40 +293,31 @@ struct Render_Graph_Runtime::State
}
}
void fail_expected(std::size_t index, External_Operation_Error error,
std::uint64_t end) noexcept {
try {
const bool first_failure = !failed.exchange(true, std::memory_order_acq_rel);
const auto status = error == External_Operation_Error::external_failure
? Node_Execution_Status::failed
: Node_Execution_Status::cancelled;
set_failed_execution(index, end, status);
{
std::lock_guard lock(error_mutex);
if (first_error == External_Operation_Error::none && !first_exception)
first_error = error;
}
if (first_failure)
cancel_pending(External_Operation_Error::cancelled);
} catch (...) {
fail_exception(index, std::current_exception(), end);
std::uint64_t end) {
const bool first_failure = !failed.exchange(true, std::memory_order_acq_rel);
const auto status = error == External_Operation_Error::external_failure
? Node_Execution_Status::failed
: Node_Execution_Status::cancelled;
set_failed_execution(index, end, status);
{
std::lock_guard lock(error_mutex);
if (first_error == External_Operation_Error::none && !first_exception)
first_error = error;
}
if (first_failure)
cancel_pending(External_Operation_Error::cancelled);
}
void fail_exception(std::size_t index, std::exception_ptr exception,
std::uint64_t end) noexcept {
std::uint64_t end) {
const bool first_failure = !failed.exchange(true, std::memory_order_acq_rel);
set_failed_execution(index, end, Node_Execution_Status::failed);
try {
{
std::lock_guard lock(error_mutex);
if (!first_exception)
first_exception = std::move(exception);
} catch (...) {
}
if (first_failure) {
try {
cancel_pending(External_Operation_Error::cancelled);
} catch (...) {
}
}
if (first_failure)
cancel_pending(External_Operation_Error::cancelled);
}
oneapi::tbb::flow::graph graph;
std::vector<Node> nodes;
@@ -341,11 +337,8 @@ struct Render_Graph_Runtime::State
};
Render_Graph_Runtime::Render_Graph_Runtime(const Render_Plan& plan)
: state_(std::make_shared<State>(plan)) {}
Render_Graph_Runtime::~Render_Graph_Runtime() {
try {
state_->cancel_pending(External_Operation_Error::cancelled);
} catch (...) {
}
Render_Graph_Runtime::~Render_Graph_Runtime() noexcept(false) {
state_->cancel_pending(External_Operation_Error::cancelled);
}
Render_Graph_Execution_Error Render_Graph_Runtime::execute(
std::span<Node_Execution* const> executions, Execute_Node execute_node) {
@@ -19,7 +19,7 @@ public:
using Execute_Node = std::function<Node_Execution_Result(
std::size_t execution_index, Node_Execution_Metrics* metrics)>;
explicit Render_Graph_Runtime(const Render_Plan& plan);
~Render_Graph_Runtime();
~Render_Graph_Runtime() noexcept(false);
Render_Graph_Runtime(const Render_Graph_Runtime&) = delete;
Render_Graph_Runtime& operator=(const Render_Graph_Runtime&) = delete;
Render_Graph_Runtime(Render_Graph_Runtime&&) = delete;
+54 -38
View File
@@ -1,5 +1,4 @@
#include "Scene_Base.hpp"
#include "renderive/error/Error_Policy.hpp"
#include <algorithm>
#include <chrono>
@@ -26,8 +25,18 @@
#include "renderive/state/base/State_Strategy_Base.hpp"
namespace {
class Render_Failure_Notification_Error final : public std::runtime_error {
public:
Render_Failure_Notification_Error(std::exception_ptr render_exception,
std::exception_ptr notification_exception)
: std::runtime_error("render failure notification also failed"),
render_exception(std::move(render_exception)),
notification_exception(std::move(notification_exception)) {}
std::exception_ptr render_exception;
std::exception_ptr notification_exception;
};
Scene_Edit_Error relationship_error(
renderive::scene::dependency::Mutation_Error error, bool display) noexcept {
renderive::scene::dependency::Mutation_Error error, bool display) {
using Error = renderive::scene::dependency::Mutation_Error;
switch (error) {
case Error::none:
@@ -41,7 +50,7 @@ Scene_Edit_Error relationship_error(
return display ? Scene_Edit_Error::display_cycle
: Scene_Edit_Error::dependency_cycle;
}
std::terminate();
throw std::logic_error("unknown scene relationship mutation error");
}
Scene_Render_Error scene_render_error(
renderive::render_graph::detail::Render_Graph_Execution_Error error) {
@@ -64,9 +73,15 @@ class Scene_Base::Execution_Context {
public:
using Message = oneapi::tbb::flow::continue_msg;
using Operation = std::function<void()>;
Execution_Context()
: node_(graph_, oneapi::tbb::flow::serial, [this](const Message&) {
execute_one();
explicit Execution_Context(Scene_Base& scene)
: scene_(scene),
node_(graph_, oneapi::tbb::flow::serial, [this](const Message&) {
try {
execute_available();
} catch (...) {
scheduled_.store(false, std::memory_order_release);
scene_.record_pending_exception(std::current_exception());
}
return Message{};
}) {
operations_.set_capacity(default_capacity);
@@ -87,15 +102,12 @@ 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)))
renderive::error::unexpected<std::logic_error>("scene execution queue rejected admitted operation");
throw std::logic_error("scene execution queue rejected admitted operation");
schedule();
}
void wait() noexcept {
try {
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
arena.execute([this] { graph_.wait_for_all(); });
} catch (...) {
}
void wait() {
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
arena.execute([this] { graph_.wait_for_all(); });
}
[[nodiscard]] Scene_Execution_Statistics statistics() const noexcept {
return {
@@ -112,31 +124,33 @@ private:
while (current < value && !peak.compare_exchange_weak(
current, value, std::memory_order_relaxed)) {}
}
void schedule() noexcept {
void schedule() {
if (scheduled_.exchange(true, std::memory_order_acq_rel))
return;
try {
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
arena.execute([this] { node_.try_put(Message{}); });
} catch (...) {
std::terminate();
auto& arena = renderive::scheduling::detail::OneTBB_Runtime::instance().arena();
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");
}
}
void execute_one() {
Operation operation;
if (operations_.try_pop(operation)) {
void execute_available() {
for (;;) {
Operation operation;
if (!operations_.try_pop(operation)) {
scheduled_.store(false, std::memory_order_release);
if (operations_.empty() ||
scheduled_.exchange(true, std::memory_order_acq_rel))
return;
continue;
}
queued_.fetch_sub(1, std::memory_order_relaxed);
slots_.release();
try {
operation();
} catch (...) {
}
operation();
}
scheduled_.store(false, std::memory_order_release);
if (!operations_.empty())
schedule();
}
static constexpr std::ptrdiff_t default_capacity = 64;
Scene_Base& scene_;
std::counting_semaphore<default_capacity> slots_{default_capacity};
oneapi::tbb::flow::graph graph_;
oneapi::tbb::concurrent_bounded_queue<Operation> operations_;
@@ -243,7 +257,7 @@ Scene_Base::Scene_Base(std::pmr::memory_resource& upstream_memory_resource,
: scene_lifetime_(std::make_shared<Scene_Lifetime>(*this)),
memory_domain_(std::allocate_shared<Scene_Memory_Domain>(std::pmr::polymorphic_allocator<Scene_Memory_Domain>(&upstream_memory_resource), upstream_memory_resource)),
impl_(std::move(impl)),
execution_context_(std::make_unique<Execution_Context>()),
execution_context_(std::make_unique<Execution_Context>(*this)),
renderables_(&memory_domain_->resource()),
dependency_resolver_(memory_domain_->resource()),
color_caches_(&memory_domain_->resource()),
@@ -295,7 +309,6 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::attach(Renderable renderable) {
const auto lifetime = renderable->d_func().real_time_data_state->scene_lifetime;
if (lifetime && lifetime.get() != scene_.scene_lifetime_.get())
return fail(Scene_Edit_Error::foreign_renderable);
renderable->d_func().bind_scene(scene_.scene_lifetime_);
auto& renderable_data = renderable->d_func();
const Renderable_Id id = renderable_data.renderable_id;
if (const auto existing = scene_.renderables_.find(id);
@@ -304,13 +317,14 @@ Scene_Edit_Error Scene_Base::Renderable_Editor::attach(Renderable renderable) {
throw std::logic_error("renderable id collision");
return Scene_Edit_Error::none;
}
auto cache = scene_.raster_capabilities_
? scene_.raster_capabilities_->make_renderable_color_cache()
: std::shared_ptr<Color_Cache>{};
renderable_data.bind_scene(scene_.scene_lifetime_);
if (!scene_.dependency_resolver_.contains(id))
scene_.dependency_resolver_.attach(id);
if (scene_.raster_capabilities_)
scene_.raster_capabilities_->attach(id);
auto cache = scene_.raster_capabilities_
? scene_.raster_capabilities_->make_renderable_color_cache()
: std::shared_ptr<Color_Cache>{};
if (!scene_.renderables_.try_emplace(id, renderable).second)
throw std::logic_error("renderable id collision");
try {
@@ -765,14 +779,14 @@ void Scene_Base::submit_operation(std::function<void()> operation) {
throw;
}
}
void Scene_Base::complete_pending_operation() noexcept {
void Scene_Base::complete_pending_operation() {
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
--pending_operations_;
}
render_completed_.notify_all();
}
void Scene_Base::record_pending_exception(std::exception_ptr exception) noexcept {
void Scene_Base::record_pending_exception(std::exception_ptr exception) {
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
if (!pending_exception_)
pending_exception_ = std::move(exception);
@@ -1127,7 +1141,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)
renderive::error::unexpected<std::logic_error>("scene raster capabilities are already bound");
throw std::logic_error("scene raster capabilities are already bound");
raster_capabilities_ = &capabilities;
}
@@ -1182,7 +1196,7 @@ bool Scene_Base::is_render_execution_context() const noexcept {
bool Scene_Base::consume_model_dirty() noexcept {
return model_dirty_.exchange(false, std::memory_order_acq_rel);
}
void Scene_Base::shutdown() noexcept {
void Scene_Base::shutdown() {
std::shared_ptr<Compiled_Render_Plan> compiled_plan;
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
@@ -1260,6 +1274,8 @@ void Scene_Base::execute_render_task(std::shared_ptr<Render_Task> 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()));
}
}
{
@@ -258,7 +258,7 @@ protected:
[[nodiscard]] bool is_render_execution_context() const noexcept;
bool consume_model_dirty() noexcept;
void invalidate_renderables();
void shutdown() noexcept;
void shutdown();
[[nodiscard]] Edit_Operation enqueue_renderable_edit(std::function<Scene_Edit_Error()> edit);
private:
@@ -301,8 +301,8 @@ private:
[[nodiscard]] Scene_Render_Error submit_render(Abstract_Frame* frame);
void submit_operation(std::function<void()> operation);
void complete_pending_operation() noexcept;
void record_pending_exception(std::exception_ptr exception) noexcept;
void complete_pending_operation();
void record_pending_exception(std::exception_ptr exception);
void capture_edit_renderable(const Renderable& renderable);
void request_render_graph_rebuild(Renderable_Base& renderable);
[[nodiscard]] std::shared_ptr<Frame_Render_Snapshot> snapshot_live_model();
@@ -18,5 +18,3 @@ static_assert(Flow_Frame_Refresh_Strategy<Frame_Control_Concept_Flow>);
TEST(frame_control_concepts_test, concepts_compile) {
SUCCEED();
}
static_assert(noexcept(std::declval<Low_Latency_Test_Strategy&>().on_real_time_data_update(std::declval<const Real_Time_Data_Observation&>())));
static_assert(noexcept(std::declval<Low_Latency_Test_Strategy&>().discard_stale_latest_data_frame()));
@@ -21,7 +21,7 @@ TEST(manual_refresh_strategy_test, publishes_only_after_manual_refresh) {
auto frame = strategy.acquire_renderer();
EXPECT_FALSE(frame);
}
EXPECT_TRUE(strategy.refresh());
EXPECT_EQ(strategy.refresh(), Manual_Refresh_Error::none);
{
auto frame = strategy.acquire_renderer();
ASSERT_TRUE(frame);
@@ -39,14 +39,14 @@ TEST(manual_refresh_strategy_test, keeps_latest_prepared_frame) {
frame->value = 2;
}
EXPECT_EQ(strategy.state().replaced_prepared_frame_count, 1);
ASSERT_TRUE(strategy.refresh());
ASSERT_EQ(strategy.refresh(), Manual_Refresh_Error::none);
auto frame = strategy.acquire_renderer();
ASSERT_TRUE(frame);
EXPECT_EQ(frame->value, 2);
}
TEST(manual_refresh_strategy_test, reports_failed_refresh_without_pending_frame) {
Manual_Refresh_Test_Strategy strategy;
EXPECT_FALSE(strategy.refresh());
EXPECT_EQ(strategy.refresh(), Manual_Refresh_Error::no_pending_frame);
EXPECT_EQ(strategy.state().failed_refresh_count, 1);
}
struct Manual_Refresh_Reentrant_Observer_Data {
@@ -82,7 +82,7 @@ TEST(manual_refresh_strategy_test, refresh_observer_can_acquire_renderer_without
observed_value.store(frame->value, std::memory_order_release);
}
};
EXPECT_TRUE(strategy.refresh());
EXPECT_EQ(strategy.refresh(), Manual_Refresh_Error::none);
EXPECT_EQ(observed_value.load(std::memory_order_acquire), 42);
}
@@ -60,18 +60,20 @@ TEST(external_operation_test, completion_before_subscription_is_delivered_once)
}
TEST(external_operation_test,
callback_exceptions_are_contained_for_both_completion_orders) {
callback_exceptions_propagate_for_both_completion_orders) {
External_Operation_Source completed_first;
const auto completed_operation = completed_first.operation();
ASSERT_TRUE(completed_first.complete());
EXPECT_NO_THROW(completed_operation.on_complete(
[](External_Operation_Completion) { throw std::runtime_error("late callback"); }));
EXPECT_THROW(completed_operation.on_complete(
[](External_Operation_Completion) {
throw std::runtime_error("late callback");
}), std::runtime_error);
External_Operation_Source subscribed_first;
const auto subscribed_operation = subscribed_first.operation();
subscribed_operation.on_complete(
[](External_Operation_Completion) { throw std::runtime_error("early callback"); });
EXPECT_TRUE(subscribed_first.complete());
EXPECT_THROW(static_cast<void>(subscribed_first.complete()), std::runtime_error);
}
TEST(render_graph_runtime_test, reusable_topology_executes_multiple_frames) {
@@ -47,10 +47,9 @@ TEST(dependency_resolver_test,
resolver.add_parent(Preparation::upload_visual,
Preparation::transform_vertices);
EXPECT_THROW(
resolver.add_parent(Preparation::acquire_source,
Preparation::upload_visual),
std::logic_error);
EXPECT_EQ(resolver.add_parent(Preparation::acquire_source,
Preparation::upload_visual).error,
renderive::scene::dependency::Mutation_Error::cycle);
EXPECT_TRUE(resolver.parents(Preparation::acquire_source).empty());
EXPECT_EQ(resolver.resolve().order,
@@ -377,7 +377,7 @@ TEST(render_plan_execution_test, capture_requests_publish_exact_immutable_abstra
ASSERT_TRUE(alternate_frame.completed_snapshot());
EXPECT_EQ(alternate_frame.completed_snapshot(), session->frames.front().snapshot);
ASSERT_GE(session->frames.size(), 2u);
EXPECT_NE(session->frames[0].snapshot->render_plan_version,
EXPECT_EQ(session->frames[0].snapshot->render_plan_version,
session->frames[1].snapshot->render_plan_version);
for (const auto& frame : session->frames) {
ASSERT_TRUE(frame.snapshot);
@@ -96,6 +96,7 @@ struct Scene3D_Playback_Snapshot_Test_Scene final
auto frame = frame_control.acquire_renderer();
ASSERT_TRUE(frame);
render(*frame);
wait_for_render();
}
Node_Execution_Result render_scene(
@@ -130,7 +130,7 @@ TEST(scene_base_test, attach_builder_can_build_complete_initial_topology) {
TEST(scene_base_test, attach_builder_rejects_null_renderable) {
Scene2D_Context<> scene;
auto builder = scene.attach_builder();
EXPECT_THROW(builder.attach({}), std::invalid_argument);
EXPECT_EQ(builder.attach({}), Scene_Edit_Error::null_renderable);
}
TEST(scene_base_test, one_runtime_callback_attaches_multiple_renderables) {
Scene2D_Context<> scene;
@@ -269,7 +269,7 @@ TEST(scene_base_test, detached_renderable_cannot_migrate_to_another_scene) {
});
EXPECT_EQ(first_scene.renderable_count(), 0u);
auto builder = second_scene.attach_builder();
EXPECT_THROW(builder.attach(renderable), std::invalid_argument);
EXPECT_EQ(builder.attach(renderable), Scene_Edit_Error::foreign_renderable);
}
TEST(scene_base_test, dependency_edit_invalidates_cached_child_prepare) {
Scene2D_Context<> scene;
@@ -476,9 +476,18 @@ TEST(scene_base_test, invalid_runtime_edit_rolls_back_complete_topology) {
EXPECT_EQ(restored.renderables, topology.renderables);
EXPECT_EQ(restored.display.size(), topology.display.size());
ASSERT_EQ(restored.dependency.size(), topology.dependency.size());
ASSERT_EQ(restored.dependency.size(), 1u);
EXPECT_EQ(restored.dependency[0].child, topology.dependency[0].child);
EXPECT_EQ(restored.dependency[0].parent, topology.dependency[0].parent);
const auto restored_edge = std::ranges::find_if(
restored.dependency, [](const auto& relationship) {
return static_cast<bool>(relationship.parent);
});
const auto original_edge = std::ranges::find_if(
topology.dependency, [](const auto& relationship) {
return static_cast<bool>(relationship.parent);
});
ASSERT_NE(restored_edge, restored.dependency.end());
ASSERT_NE(original_edge, topology.dependency.end());
EXPECT_EQ(restored_edge->child, original_edge->child);
EXPECT_EQ(restored_edge->parent, original_edge->parent);
scene.render();
EXPECT_NO_THROW(scene.wait_for_render());
EXPECT_EQ(parent->render_count, 1);
@@ -214,7 +214,7 @@ TEST(threading_contract_test, manual_strategy_keeps_frames_consistent_during_con
std::thread refresher([&] {
wait_start(start);
for (;;) {
strategy.refresh();
static_cast<void>(strategy.refresh());
const auto state = strategy.state();
if (producers_done.load(std::memory_order_acquire) == producer_count && !state.pending_frame) {
break;