diff --git a/Kernel/src/renderive/base/observer/Observer_State.hpp b/Kernel/src/renderive/base/observer/Observer_State.hpp index a1a9e44..8a838a2 100644 --- a/Kernel/src/renderive/base/observer/Observer_State.hpp +++ b/Kernel/src/renderive/base/observer/Observer_State.hpp @@ -17,7 +17,7 @@ public: Observer_State(Observer observer, Time_Source time_source) : observer_(std::move(observer)), time_source_(std::move(time_source)) {} Observer_State(const Observer_State&) = delete; Observer_State& operator=(const Observer_State&) = delete; - Observer_State(Observer_State&& other) noexcept requires std::move_constructible && std::move_constructible + Observer_State(Observer_State&& other) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v && std::is_nothrow_default_constructible_v) requires std::move_constructible && std::move_constructible : observer_(std::move(other.observer_)), time_source_(std::move(other.time_source_)) {} Observer_State& operator=(Observer_State&&) = delete; std::uint64_t now_ns() const noexcept { diff --git a/Kernel/src/renderive/frame_control/base/Frame_Control_Strategy_Base.hpp b/Kernel/src/renderive/frame_control/base/Frame_Control_Strategy_Base.hpp index 83e0c72..ddf28ca 100644 --- a/Kernel/src/renderive/frame_control/base/Frame_Control_Strategy_Base.hpp +++ b/Kernel/src/renderive/frame_control/base/Frame_Control_Strategy_Base.hpp @@ -18,8 +18,8 @@ public: virtual double frequency_hz() const noexcept = 0; virtual std::uint64_t next_refresh_interval_ns() const noexcept = 0; virtual State frame_control_state() const = 0; - virtual void on_real_time_data_update(const Real_Time_Data_Observation& observation) = 0; - virtual bool discard_stale_latest_data_frame() { + virtual void on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept = 0; + virtual bool discard_stale_latest_data_frame() noexcept { return false; } static constexpr double invalid_frequency_hz() noexcept { diff --git a/Kernel/src/renderive/frame_control/concept/Real_Time_Data_Aware_Strategy.hpp b/Kernel/src/renderive/frame_control/concept/Real_Time_Data_Aware_Strategy.hpp index 3f21959..820d45f 100644 --- a/Kernel/src/renderive/frame_control/concept/Real_Time_Data_Aware_Strategy.hpp +++ b/Kernel/src/renderive/frame_control/concept/Real_Time_Data_Aware_Strategy.hpp @@ -4,5 +4,5 @@ #include "renderive/real_time_data/Observation.hpp" template concept Real_Time_Data_Aware_Frame_Strategy = Frame_Control_Strategy && requires(That& strategy, const Real_Time_Data_Observation& observation) { - { strategy.on_real_time_data_update(observation) } -> std::same_as; + { strategy.on_real_time_data_update(observation) } noexcept -> std::same_as; }; diff --git a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp index fa0cf67..b8e44fe 100644 --- a/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp +++ b/Kernel/src/renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp @@ -103,7 +103,7 @@ public: Frame_Control_Strategy_Base::State frame_control_state() const override; std::size_t pending_frame_count() const; State state() const; - void on_real_time_data_update(const Real_Time_Data_Observation& observation) override; + void on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept override; private: std::uint64_t now_ns() const noexcept; void observe(const Observation& observation) noexcept; 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 869872d..0641f9a 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 @@ -190,7 +190,7 @@ auto Flow_Refresh_Strategy::state() const -> State return result; } template -void Flow_Refresh_Strategy::on_real_time_data_update(const Real_Time_Data_Observation& observation) { +void Flow_Refresh_Strategy::on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept { Observation strategy_observation; { std::lock_guard lock(state_mutex_); diff --git a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp index bfbe9f9..b1bd2fc 100644 --- a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp +++ b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp @@ -148,10 +148,10 @@ public: Frame_Control_Strategy_Base::State frame_control_state() const override; State state() const; Counter_Statistics counter_statistics() const; - void on_real_time_data_update(const Real_Time_Data_Observation& observation) override; + void on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept override; bool discard_pending_frame(); bool discard_pending_frame_before(std::uint64_t real_time_data_update_sequence); - bool discard_stale_latest_data_frame() override; + bool discard_stale_latest_data_frame() noexcept override; private: static double checked_frequency_hz(double frequency_hz); std::uint64_t now_ns() const noexcept; 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 69e9504..587f761 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 @@ -250,7 +250,7 @@ auto Low_Latency_Strategy::counter_statistics() co return counters_; } template -void Low_Latency_Strategy::on_real_time_data_update(const Real_Time_Data_Observation& observation) { +void Low_Latency_Strategy::on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept { Observation strategy_observation; { std::lock_guard lock(state_mutex_); @@ -290,7 +290,7 @@ bool Low_Latency_Strategy::discard_pending_frame_b return discarded; } template -bool Low_Latency_Strategy::discard_stale_latest_data_frame() { +bool Low_Latency_Strategy::discard_stale_latest_data_frame() noexcept { Observation observation; bool discarded{}; { diff --git a/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp b/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp index 33f9c63..8ce804f 100644 --- a/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp +++ b/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp @@ -100,7 +100,7 @@ public: bool refresh(); bool discard_pending_frame(); State state() const; - void on_real_time_data_update(const Real_Time_Data_Observation& observation) override; + void on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept override; private: std::uint64_t now_ns() const noexcept; void observe(const Observation& observation) noexcept; diff --git a/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.inl b/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.inl index 5ec5f4b..80e6a11 100644 --- a/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.inl +++ b/Kernel/src/renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.inl @@ -195,7 +195,7 @@ auto Manual_Refresh_Strategy::state() const -> Sta return state_; } template -void Manual_Refresh_Strategy::on_real_time_data_update(const Real_Time_Data_Observation& observation) { +void Manual_Refresh_Strategy::on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept { Observation strategy_observation; { std::lock_guard lock(state_mutex_); 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 9f91e2b..f3e0ed4 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 @@ -13,7 +13,14 @@ #include "renderive/renderable/concept/Renderable.hpp" #include "renderive/scene/base/Scene_Base.hpp" #include "renderive/scene/base/Scene_Render_Context.hpp" +template +inline constexpr bool Unique_Real_Time_Data_Types_Value = true; +template +inline constexpr bool Unique_Real_Time_Data_Types_Value = (!std::same_as && ...) && Unique_Real_Time_Data_Types_Value; +template +concept Unique_Real_Time_Data_Types = Unique_Real_Time_Data_Types_Value; template +requires Unique_Real_Time_Data_Types struct With_Real_Time_Data { explicit With_Real_Time_Data(std::shared_ptr... data) : data(std::move(data)...) { if (!std::apply([](const auto&... source) { @@ -36,6 +43,7 @@ enum class Real_Time_Data_Discard_Mode { retain_frame_interval }; template +requires Unique_Real_Time_Data_Types class Attach_Real_Time_Data : public Renderable_Type { public: template diff --git a/Kernel/src/renderive/real_time_data/History_Real_Time_Data.inl b/Kernel/src/renderive/real_time_data/History_Real_Time_Data.inl index 23d213b..6728aab 100644 --- a/Kernel/src/renderive/real_time_data/History_Real_Time_Data.inl +++ b/Kernel/src/renderive/real_time_data/History_Real_Time_Data.inl @@ -24,11 +24,18 @@ void History_Real_Time_Data::update(Valu Real_Time_Data_Observation observation; { std::lock_guard lock(mutex_); - values_.push_back(std::move(value)); + update_times_.push_back(0); + try { + values_.push_back(std::move(value)); + } catch (...) { + update_times_.pop_back(); + throw; + } + const std::uint64_t update_time_ns = observer_.now_ns(); + update_times_.back() = update_time_ns; ++revision_; ++total_update_count_; - last_update_time_ns_ = observer_.now_ns(); - update_times_.push_back(last_update_time_ns_); + last_update_time_ns_ = update_time_ns; observation = {Real_Time_Data_Observation_Event::updated, {this, Real_Time_Data_Retention::history, revision_, last_update_time_ns_, total_update_count_, values_.size()}}; } observer_.observe(observation); diff --git a/Kernel/src/renderive/scene/Scene2D_Context.hpp b/Kernel/src/renderive/scene/Scene2D_Context.hpp index 1a8a86d..1324e3d 100644 --- a/Kernel/src/renderive/scene/Scene2D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene2D_Context.hpp @@ -21,7 +21,7 @@ template concept Scene2D_Frame_Control_Constructible = std::constructible_from || std::constructible_from; template , Color_Cache_Type Cache = Recording_Color_Cache, class State = Scene2D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>> requires Frame_Control_Strategy_For && State_Value -class Scene2D_Context : public Triple_State_Strategy { +class Scene2D_Context final : public Triple_State_Strategy { public: using Scene_State_Strategy = Triple_State_Strategy; using Render_Task = Scene_Base::Render_Task; @@ -73,17 +73,29 @@ protected: return &dependency_root_; } void on_renderable_attached(Renderable_Base& renderable) override { + Cache_Pointer cache = make_cache_pointer(this->memory_resource()); auto& display_node = Scene_Base::layer_node(renderable); auto& dependency_node = Scene_Base::dependency_node(renderable); - if (!display_node.parent()) { - display_root_.append_child(display_node); - } - if (!dependency_node.parent()) { - dependency_root_.append_child(dependency_node); - } - if (color_caches_.try_emplace(&renderable, make_cache_pointer(this->memory_resource())).second) { - renderable.invalidate_cache(); + const bool attach_display = !display_node.parent(); + const bool attach_dependency = !dependency_node.parent(); + try { + if (attach_display) { + display_root_.append_child(display_node); + } + if (attach_dependency) { + dependency_root_.append_child(dependency_node); + } + color_caches_.try_emplace(&renderable, std::move(cache)); + } catch (...) { + if (attach_dependency) { + dependency_node.detach(); + } + if (attach_display) { + display_node.detach(); + } + throw; } + renderable.invalidate_cache(); } void on_renderable_detached(Renderable_Base& renderable) override { promote_children(Scene_Base::layer_node(renderable)); diff --git a/Kernel/src/renderive/scene/Scene3D_Context.hpp b/Kernel/src/renderive/scene/Scene3D_Context.hpp index 0557020..f867e81 100644 --- a/Kernel/src/renderive/scene/Scene3D_Context.hpp +++ b/Kernel/src/renderive/scene/Scene3D_Context.hpp @@ -14,7 +14,7 @@ template concept Scene3D_Frame_Control_Constructible = std::constructible_from || std::constructible_from; template , class State = Scene3D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>> requires Frame_Control_Strategy_For && State_Value -class Scene3D_Context : public Triple_State_Strategy { +class Scene3D_Context final : public Triple_State_Strategy { public: using Scene_State_Strategy = Triple_State_Strategy; using Frame_Control = Strategy; diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index 47b3497..37578e3 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -117,8 +117,9 @@ void Scene_Base::attach_renderable(Renderable renderable) { if (is_renderable_attached_locked(*renderable)) { return; } - cache_renderables_->push_back(renderable); + cache_renderables_->reserve(cache_renderables_->size() + 1); on_renderable_attached(*renderable); + cache_renderables_->push_back(std::move(renderable)); } void Scene_Base::detach_renderable(Renderable_Base& renderable) { auto task_lock = lock_render_idle(); diff --git a/Kernel/src/renderive/state/Double_State_Strategy.hpp b/Kernel/src/renderive/state/Double_State_Strategy.hpp index d2d800c..6d24875 100644 --- a/Kernel/src/renderive/state/Double_State_Strategy.hpp +++ b/Kernel/src/renderive/state/Double_State_Strategy.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include "renderive/base/Atomic_Mutex.hpp" @@ -25,17 +26,17 @@ struct Double_State_Strategy : That, State_Strategy_Base { State state; }; static_assert(Timed_Struct_Observer); - Double_State_Strategy() requires std::default_initializable && std::default_initializable : That(), states{}, render_state(&states[0]), cache_state(&states[1]) {} + Double_State_Strategy() requires std::default_initializable && std::default_initializable : That(), states{}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} explicit Double_State_Strategy(With_Observer option) requires std::default_initializable && std::default_initializable - : That(), observer(std::move(option.observer)), states{}, render_state(&states[0]), cache_state(&states[1]) {} + : That(), observer(std::move(option.observer)), states{}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} template requires std::constructible_from explicit Double_State_Strategy(const State& state, Args&&... args) - : That(std::forward(args)...), states{state, state}, render_state(&states[0]), cache_state(&states[1]) {} + : That(std::forward(args)...), states{state, state, state}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} template requires std::constructible_from Double_State_Strategy(const State& state, With_Observer option, Args&&... args) - : That(std::forward(args)...), observer(std::move(option.observer)), states{state, state}, render_state(&states[0]), cache_state(&states[1]) {} + : That(std::forward(args)...), observer(std::move(option.observer)), states{state, state, state}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {} template Value> Self& set(Value&& value) { Observation observation; @@ -59,15 +60,15 @@ struct Double_State_Strategy : That, State_Strategy_Base { } } void publish() override { - Observation observation; + std::optional observation; { std::lock_guard lock(mtx); - std::swap(render_state, cache_state); - *cache_state = *render_state; + *scratch_state = *cache_state; + observation.emplace(Observation_Event::published, observer.now_ns(), cache_update_count, publish_count + 1, *cache_state); + std::swap(render_state, scratch_state); ++publish_count; - observation = {Observation_Event::published, observer.now_ns(), cache_update_count, publish_count, *render_state}; } - observer.observe(observation); + observer.observe(*observation); } std::uint64_t state_revision() const override { std::lock_guard lock(mtx); @@ -79,9 +80,10 @@ struct Double_State_Strategy : That, State_Strategy_Base { } private: Observer observer; - State states[2]; + State states[3]; State* render_state; State* cache_state; + State* scratch_state; std::uint64_t cache_update_count{}; std::uint64_t publish_count{}; mutable Mutex mtx; diff --git a/Kernel/src/renderive/state/Triple_State_Strategy.hpp b/Kernel/src/renderive/state/Triple_State_Strategy.hpp index ba96ff5..6c3cd44 100644 --- a/Kernel/src/renderive/state/Triple_State_Strategy.hpp +++ b/Kernel/src/renderive/state/Triple_State_Strategy.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include "renderive/base/Atomic_Mutex.hpp" @@ -28,17 +29,17 @@ struct Triple_State_Strategy : That, State_Strategy_Base { }; static_assert(Timed_Struct_Observer); Triple_State_Strategy() requires std::default_initializable && std::default_initializable - : That(), states{}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]) {} + : That(), states{}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} explicit Triple_State_Strategy(With_Observer option) requires std::default_initializable && std::default_initializable - : That(), observer(std::move(option.observer)), states{}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]) {} + : That(), observer(std::move(option.observer)), states{}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} template requires std::constructible_from explicit Triple_State_Strategy(const State& state, Args&&... args) - : That(std::forward(args)...), states{state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]) {} + : That(std::forward(args)...), states{state, state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} template requires std::constructible_from Triple_State_Strategy(const State& state, With_Observer option, Args&&... args) - : That(std::forward(args)...), observer(std::move(option.observer)), states{state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]) {} + : That(std::forward(args)...), observer(std::move(option.observer)), states{state, state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {} template Value> Self& set(Value&& value) { Observation observation; @@ -62,33 +63,31 @@ struct Triple_State_Strategy : That, State_Strategy_Base { } } void publish() override { - Observation observation; + std::optional observation; { std::lock_guard lock(mtx); - std::swap(published_state, cache_state); - *cache_state = *published_state; + *scratch_state = *cache_state; + observation.emplace(Observation_Event::published, observer.now_ns(), cache_update_count, publish_count + 1, render_revision, *cache_state); + std::swap(published_state, scratch_state); ++publish_count; published_revision = publish_count; - observation = {Observation_Event::published, observer.now_ns(), cache_update_count, publish_count, render_revision, *published_state}; } - observer.observe(observation); + observer.observe(*observation); } std::uint64_t acquire_render_state() { - Observation observation; - bool acquired{}; + std::optional observation; std::uint64_t revision{}; { std::lock_guard lock(mtx); if (render_revision != published_revision) { + observation.emplace(Observation_Event::render_acquired, observer.now_ns(), cache_update_count, publish_count, published_revision, *published_state); std::swap(render_state, published_state); render_revision = published_revision; - acquired = true; - observation = {Observation_Event::render_acquired, observer.now_ns(), cache_update_count, publish_count, render_revision, *render_state}; } revision = render_revision; } - if (acquired) { - observer.observe(observation); + if (observation) { + observer.observe(*observation); } return revision; } @@ -102,10 +101,11 @@ struct Triple_State_Strategy : That, State_Strategy_Base { } private: Observer observer; - State states[3]; + State states[4]; State* render_state; State* published_state; State* cache_state; + State* scratch_state; std::uint64_t cache_update_count{}; std::uint64_t publish_count{}; std::uint64_t published_revision{}; diff --git a/Kernel/tests/renderive/base/observer/Observer_Test.cpp b/Kernel/tests/renderive/base/observer/Observer_Test.cpp index ef402b1..bd31046 100644 --- a/Kernel/tests/renderive/base/observer/Observer_Test.cpp +++ b/Kernel/tests/renderive/base/observer/Observer_Test.cpp @@ -81,3 +81,20 @@ TEST(observer_state_test, serializes_time_source_access) { EXPECT_FALSE(data->overlap.load(std::memory_order_acquire)); EXPECT_EQ(data->value, 8); } +struct Observer_Test_Throwing_Move_Time_Source { + Observer_Test_Throwing_Move_Time_Source() = default; + Observer_Test_Throwing_Move_Time_Source(Observer_Test_Throwing_Move_Time_Source&&) { + throw std::runtime_error("time source move failed"); + } + Observer_Test_Throwing_Move_Time_Source& operator=(Observer_Test_Throwing_Move_Time_Source&&) = delete; + std::uint64_t now_ns() const noexcept { + return 0; + } +}; +TEST(observer_state_test, move_constructor_propagates_throwing_time_source_move) { + using State = Observer_State; + static_assert(std::move_constructible); + static_assert(!std::is_nothrow_move_constructible_v); + State source; + EXPECT_THROW(State(std::move(source)), std::runtime_error); +} diff --git a/Kernel/tests/renderive/frame_control/concept/Concepts_Test.cpp b/Kernel/tests/renderive/frame_control/concept/Concepts_Test.cpp index 34a92fd..2300a90 100644 --- a/Kernel/tests/renderive/frame_control/concept/Concepts_Test.cpp +++ b/Kernel/tests/renderive/frame_control/concept/Concepts_Test.cpp @@ -18,3 +18,5 @@ static_assert(Flow_Frame_Refresh_Strategy); TEST(frame_control_concepts_test, concepts_compile) { SUCCEED(); } +static_assert(noexcept(std::declval().on_real_time_data_update(std::declval()))); +static_assert(noexcept(std::declval().discard_stale_latest_data_frame())); diff --git a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp index a640e98..bacf018 100644 --- a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp +++ b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp @@ -224,3 +224,26 @@ TEST(real_time_data_attachment_test, frame_strategy_observer_can_reacquire_scene latest->update(1); EXPECT_TRUE(reacquired.load(std::memory_order_acquire)); } +class Real_Time_Data_Failing_Memory_Resource : public std::pmr::memory_resource { +private: + void* do_allocate(std::size_t, std::size_t) override { + throw std::bad_alloc(); + } + void do_deallocate(void*, std::size_t, std::size_t) override {} + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { + return this == &other; + } +}; +TEST(history_real_time_data_test, failed_timestamp_allocation_rolls_back_value_and_revision) { + Real_Time_Data_Failing_Memory_Resource memory_resource; + History_Real_Time_Data data(memory_resource); + EXPECT_THROW(data.update(7), std::bad_alloc); + EXPECT_TRUE(data.snapshot().empty()); + EXPECT_EQ(data.revision(), 0); + const auto state = data.update_state(); + EXPECT_EQ(state.total_update_count, 0); + EXPECT_EQ(state.retained_value_count, 0); + EXPECT_EQ(state.update_time_ns, 0); +} +static_assert(Unique_Real_Time_Data_Types); +static_assert(!Unique_Real_Time_Data_Types); diff --git a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp index 6cdbabe..7eec0dc 100644 --- a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp @@ -157,3 +157,26 @@ TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_ } } } +struct Scene2D_Attach_Throwing_Cache : Color_Cache { + explicit Scene2D_Attach_Throwing_Cache(std::pmr::memory_resource&) { + if (++construction_count == throw_on_construction) { + throw std::runtime_error("cache construction failed"); + } + } + void clear() override {} + void composite(const Color_Cache&) override {} + inline static int construction_count{}; + inline static int throw_on_construction{}; +}; +TEST(scene2d_context_test, failed_attach_leaves_renderable_fully_detached) { + Scene2D_Attach_Throwing_Cache::construction_count = 0; + Scene2D_Attach_Throwing_Cache::throw_on_construction = 2; + Scene2D_Context, Scene2D_Attach_Throwing_Cache> scene; + auto renderable = std::make_shared(scene); + EXPECT_THROW(scene.attach_renderable(renderable), std::runtime_error); + EXPECT_EQ(scene.renderable_count(), 0); + EXPECT_TRUE(scene.topology_snapshot().renderables.empty()); + Scene2D_Attach_Throwing_Cache::throw_on_construction = 0; + EXPECT_NO_THROW(scene.attach_renderable(renderable)); + EXPECT_EQ(scene.renderable_count(), 1); +} diff --git a/Kernel/tests/renderive/scene/concept/Scene_Test.cpp b/Kernel/tests/renderive/scene/concept/Scene_Test.cpp index 18780c7..46bc3d0 100644 --- a/Kernel/tests/renderive/scene/concept/Scene_Test.cpp +++ b/Kernel/tests/renderive/scene/concept/Scene_Test.cpp @@ -8,3 +8,5 @@ static_assert(!Scene_3D>); TEST(scene_concept_test, accepts_2d_and_3d_contexts) { SUCCEED(); } +static_assert(std::is_final_v>); +static_assert(std::is_final_v>); diff --git a/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp b/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp index c69ea18..e3446ce 100644 --- a/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp +++ b/Kernel/tests/renderive/state/Double_State_Strategy_Test.cpp @@ -76,3 +76,38 @@ TEST(double_state_strategy_test, notifies_observer_for_cache_update_and_publish) EXPECT_EQ(recorder.data->publish_count, 1); EXPECT_EQ(recorder.data->value, 8); } +struct Double_State_Throwing_Assignment_State { + int first{}; + int second{}; + Double_State_Throwing_Assignment_State() = default; + Double_State_Throwing_Assignment_State(int first, int second) : first(first), second(second) {} + Double_State_Throwing_Assignment_State(const Double_State_Throwing_Assignment_State&) = default; + Double_State_Throwing_Assignment_State(Double_State_Throwing_Assignment_State&&) noexcept = default; + Double_State_Throwing_Assignment_State& operator=(const Double_State_Throwing_Assignment_State& other) { + first = other.first; + if (throw_on_copy_assignment) { + throw std::runtime_error("state assignment failed"); + } + second = other.second; + return *this; + } + Double_State_Throwing_Assignment_State& operator=(Double_State_Throwing_Assignment_State&&) noexcept = default; + inline static bool throw_on_copy_assignment{}; +}; +TEST(double_state_strategy_test, failed_publish_keeps_render_state_and_revision_unchanged) { + using Strategy = Double_State_Strategy; + Double_State_Throwing_Assignment_State::throw_on_copy_assignment = false; + Strategy strategy(Double_State_Throwing_Assignment_State(1, 2)); + strategy.set<&Double_State_Throwing_Assignment_State::first>(10); + strategy.set<&Double_State_Throwing_Assignment_State::second>(20); + Double_State_Throwing_Assignment_State::throw_on_copy_assignment = true; + EXPECT_THROW(strategy.publish(), std::runtime_error); + Double_State_Throwing_Assignment_State::throw_on_copy_assignment = false; + EXPECT_EQ(strategy.state_revision(), 0); + EXPECT_EQ(strategy.render_use_state().first, 1); + EXPECT_EQ(strategy.render_use_state().second, 2); + strategy.publish(); + EXPECT_EQ(strategy.state_revision(), 1); + EXPECT_EQ(strategy.render_use_state().first, 10); + EXPECT_EQ(strategy.render_use_state().second, 20); +} diff --git a/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp b/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp index cf63bcc..6762893 100644 --- a/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp +++ b/Kernel/tests/renderive/state/Triple_State_Strategy_Test.cpp @@ -66,3 +66,40 @@ TEST(triple_state_strategy_test, concurrently_acquires_render_revision_without_d EXPECT_EQ(strategy.acquire_render_state(), 1000); EXPECT_EQ(strategy.render_use_state().value, 1000); } +struct Triple_State_Throwing_Assignment_State { + int first{}; + int second{}; + Triple_State_Throwing_Assignment_State() = default; + Triple_State_Throwing_Assignment_State(int first, int second) : first(first), second(second) {} + Triple_State_Throwing_Assignment_State(const Triple_State_Throwing_Assignment_State&) = default; + Triple_State_Throwing_Assignment_State(Triple_State_Throwing_Assignment_State&&) noexcept = default; + Triple_State_Throwing_Assignment_State& operator=(const Triple_State_Throwing_Assignment_State& other) { + first = other.first; + if (throw_on_copy_assignment) { + throw std::runtime_error("state assignment failed"); + } + second = other.second; + return *this; + } + Triple_State_Throwing_Assignment_State& operator=(Triple_State_Throwing_Assignment_State&&) noexcept = default; + inline static bool throw_on_copy_assignment{}; +}; +TEST(triple_state_strategy_test, failed_publish_keeps_published_revision_and_render_state_unchanged) { + using Strategy = Triple_State_Strategy; + Triple_State_Throwing_Assignment_State::throw_on_copy_assignment = false; + Strategy strategy(Triple_State_Throwing_Assignment_State(1, 2)); + strategy.set<&Triple_State_Throwing_Assignment_State::first>(10); + strategy.set<&Triple_State_Throwing_Assignment_State::second>(20); + Triple_State_Throwing_Assignment_State::throw_on_copy_assignment = true; + EXPECT_THROW(strategy.publish(), std::runtime_error); + Triple_State_Throwing_Assignment_State::throw_on_copy_assignment = false; + EXPECT_EQ(strategy.state_revision(), 0); + EXPECT_EQ(strategy.acquire_render_state(), 0); + EXPECT_EQ(strategy.render_use_state().first, 1); + EXPECT_EQ(strategy.render_use_state().second, 2); + strategy.publish(); + EXPECT_EQ(strategy.state_revision(), 1); + EXPECT_EQ(strategy.acquire_render_state(), 1); + EXPECT_EQ(strategy.render_use_state().first, 10); + EXPECT_EQ(strategy.render_use_state().second, 20); +}