错误处理
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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()));
|
||||
|
||||
+4
-4
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user