内核优化

This commit is contained in:
2026-08-10 17:25:45 +08:00
parent c677c65f0e
commit 602e73f3b3
23 changed files with 221 additions and 52 deletions
@@ -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<Observer> && std::move_constructible<Time_Source>
Observer_State(Observer_State&& other) noexcept(std::is_nothrow_move_constructible_v<Observer> && std::is_nothrow_move_constructible_v<Time_Source> && std::is_nothrow_default_constructible_v<Mutex>) requires std::move_constructible<Observer> && std::move_constructible<Time_Source>
: 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 {
@@ -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 {
@@ -4,5 +4,5 @@
#include "renderive/real_time_data/Observation.hpp"
template <class That>
concept Real_Time_Data_Aware_Frame_Strategy = Frame_Control_Strategy<That> && requires(That& strategy, const Real_Time_Data_Observation& observation) {
{ strategy.on_real_time_data_update(observation) } -> std::same_as<void>;
{ strategy.on_real_time_data_update(observation) } noexcept -> std::same_as<void>;
};
@@ -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;
@@ -190,7 +190,7 @@ auto Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::state() const -> State
return result;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::on_real_time_data_update(const Real_Time_Data_Observation& observation) {
void Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>::on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept {
Observation strategy_observation;
{
std::lock_guard<Mutex> lock(state_mutex_);
@@ -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;
@@ -250,7 +250,7 @@ auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::counter_statistics() co
return counters_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::on_real_time_data_update(const Real_Time_Data_Observation& observation) {
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept {
Observation strategy_observation;
{
std::lock_guard<Mutex> lock(state_mutex_);
@@ -290,7 +290,7 @@ bool Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::discard_pending_frame_b
return discarded;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
bool Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::discard_stale_latest_data_frame() {
bool Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::discard_stale_latest_data_frame() noexcept {
Observation observation;
bool discarded{};
{
@@ -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;
@@ -195,7 +195,7 @@ auto Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::state() const -> Sta
return state_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::on_real_time_data_update(const Real_Time_Data_Observation& observation) {
void Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>::on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept {
Observation strategy_observation;
{
std::lock_guard<Mutex> lock(state_mutex_);
@@ -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 <class... Data>
inline constexpr bool Unique_Real_Time_Data_Types_Value = true;
template <class Data, class... Rest>
inline constexpr bool Unique_Real_Time_Data_Types_Value<Data, Rest...> = (!std::same_as<Data, Rest> && ...) && Unique_Real_Time_Data_Types_Value<Rest...>;
template <class... Data>
concept Unique_Real_Time_Data_Types = Unique_Real_Time_Data_Types_Value<Data...>;
template <Real_Time_Data... Data>
requires Unique_Real_Time_Data_Types<Data...>
struct With_Real_Time_Data {
explicit With_Real_Time_Data(std::shared_ptr<Data>... 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 <Renderable Renderable_Type, Real_Time_Data... Data>
requires Unique_Real_Time_Data_Types<Data...>
class Attach_Real_Time_Data : public Renderable_Type {
public:
template <class... Args>
@@ -24,11 +24,18 @@ void History_Real_Time_Data<Value_Type, Container, Mutex, Observer>::update(Valu
Real_Time_Data_Observation observation;
{
std::lock_guard<Mutex> 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);
+21 -9
View File
@@ -21,7 +21,7 @@ template <class Frame_Control, class... Args>
concept Scene2D_Frame_Control_Constructible = std::constructible_from<Frame_Control, Args...> || std::constructible_from<Frame_Control, std::pmr::memory_resource&, Args...>;
template <class Strategy = Low_Latency_Strategy<Scene2D_Frame_Data>, 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<Strategy, Scene2D_Frame_Data> && State_Value<State>
class Scene2D_Context : public Triple_State_Strategy<Scene_2D_Base, State, Atomic_Spin_Mutex, State_Observer> {
class Scene2D_Context final : public Triple_State_Strategy<Scene_2D_Base, State, Atomic_Spin_Mutex, State_Observer> {
public:
using Scene_State_Strategy = Triple_State_Strategy<Scene_2D_Base, State, Atomic_Spin_Mutex, State_Observer>;
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));
@@ -14,7 +14,7 @@ template <class Frame_Control, class... Args>
concept Scene3D_Frame_Control_Constructible = std::constructible_from<Frame_Control, Args...> || std::constructible_from<Frame_Control, std::pmr::memory_resource&, Args...>;
template <class Strategy = Low_Latency_Strategy<Scene3D_Frame_Data>, class State = Scene3D_State, class State_Observer = Observer_State<>, class Scene_Observer = Observer_State<>>
requires Frame_Control_Strategy_For<Strategy, Scene3D_Frame_Data> && State_Value<State>
class Scene3D_Context : public Triple_State_Strategy<Scene_3D_Base, State, Atomic_Spin_Mutex, State_Observer> {
class Scene3D_Context final : public Triple_State_Strategy<Scene_3D_Base, State, Atomic_Spin_Mutex, State_Observer> {
public:
using Scene_State_Strategy = Triple_State_Strategy<Scene_3D_Base, State, Atomic_Spin_Mutex, State_Observer>;
using Frame_Control = Strategy;
@@ -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();
@@ -1,6 +1,7 @@
#pragma once
#include <concepts>
#include <mutex>
#include <optional>
#include <type_traits>
#include <utility>
#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<Observer, Observation>);
Double_State_Strategy() requires std::default_initializable<That> && std::default_initializable<State> : That(), states{}, render_state(&states[0]), cache_state(&states[1]) {}
Double_State_Strategy() requires std::default_initializable<That> && std::default_initializable<State> : That(), states{}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {}
explicit Double_State_Strategy(With_Observer<Observer> option) requires std::default_initializable<That> && std::default_initializable<State>
: 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 <class... Args>
requires std::constructible_from<That, Args&&...>
explicit Double_State_Strategy(const State& state, Args&&... args)
: That(std::forward<Args>(args)...), states{state, state}, render_state(&states[0]), cache_state(&states[1]) {}
: That(std::forward<Args>(args)...), states{state, state, state}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {}
template <class... Args>
requires std::constructible_from<That, Args&&...>
Double_State_Strategy(const State& state, With_Observer<Observer> option, Args&&... args)
: That(std::forward<Args>(args)...), observer(std::move(option.observer)), states{state, state}, render_state(&states[0]), cache_state(&states[1]) {}
: That(std::forward<Args>(args)...), observer(std::move(option.observer)), states{state, state, state}, render_state(&states[0]), cache_state(&states[1]), scratch_state(&states[2]) {}
template <auto Member, Property_Member_Assignable<State, Member> 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> 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;
@@ -1,6 +1,7 @@
#pragma once
#include <concepts>
#include <mutex>
#include <optional>
#include <type_traits>
#include <utility>
#include "renderive/base/Atomic_Mutex.hpp"
@@ -28,17 +29,17 @@ struct Triple_State_Strategy : That, State_Strategy_Base {
};
static_assert(Timed_Struct_Observer<Observer, Observation>);
Triple_State_Strategy() requires std::default_initializable<That> && std::default_initializable<State>
: 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<Observer> option) requires std::default_initializable<That> && std::default_initializable<State>
: 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 <class... Args>
requires std::constructible_from<That, Args&&...>
explicit Triple_State_Strategy(const State& state, Args&&... args)
: That(std::forward<Args>(args)...), states{state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]) {}
: That(std::forward<Args>(args)...), states{state, state, state, state}, render_state(&states[0]), published_state(&states[1]), cache_state(&states[2]), scratch_state(&states[3]) {}
template <class... Args>
requires std::constructible_from<That, Args&&...>
Triple_State_Strategy(const State& state, With_Observer<Observer> option, Args&&... args)
: That(std::forward<Args>(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>(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 <auto Member, Property_Member_Assignable<State, Member> 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> 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> 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{};
@@ -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<Disabled_Observer, Observer_Test_Throwing_Move_Time_Source>;
static_assert(std::move_constructible<Observer_Test_Throwing_Move_Time_Source>);
static_assert(!std::is_nothrow_move_constructible_v<State>);
State source;
EXPECT_THROW(State(std::move(source)), std::runtime_error);
}
@@ -18,3 +18,5 @@ 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()));
@@ -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<int> 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<Real_Time_Data_Test_Latest, Real_Time_Data_Test_History>);
static_assert(!Unique_Real_Time_Data_Types<Real_Time_Data_Test_Latest, Real_Time_Data_Test_Latest>);
@@ -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<Low_Latency_Strategy<Scene2D_Frame_Data>, Scene2D_Attach_Throwing_Cache> scene;
auto renderable = std::make_shared<Scene2D_Context_Test_Renderable>(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);
}
@@ -8,3 +8,5 @@ static_assert(!Scene_3D<Scene2D_Context<>>);
TEST(scene_concept_test, accepts_2d_and_3d_contexts) {
SUCCEED();
}
static_assert(std::is_final_v<Scene2D_Context<>>);
static_assert(std::is_final_v<Scene3D_Context<>>);
@@ -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<State_Plain_Base, Double_State_Throwing_Assignment_State>;
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);
}
@@ -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_Test_Base, Triple_State_Throwing_Assignment_State>;
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);
}