188 lines
7.8 KiB
C++
188 lines
7.8 KiB
C++
#include "double_buffer/model.hpp"
|
|
#include <gtest/gtest.h>
|
|
namespace {
|
|
struct Object_Buffer_Tag {};
|
|
struct Test_Object : double_buffer::Def<Test_Object, double_buffer::Root, double_buffer::Tagged_Buffer<Object_Buffer_Tag, int>> {
|
|
struct Prop : Prev_Prop {
|
|
int first{};
|
|
int second{};
|
|
};
|
|
struct State : Prev_State {
|
|
int first{};
|
|
int second{};
|
|
bool operator==(const State&) const = default;
|
|
};
|
|
struct Private : Prev_Private {};
|
|
auto& data_for_test();
|
|
};
|
|
using Object = double_buffer::Impl<Test_Object>;
|
|
inline auto& Test_Object::data_for_test() {
|
|
return static_cast<Object::Private&>(*d);
|
|
}
|
|
struct Derived_Object : double_buffer::Def<Derived_Object, Test_Object> {
|
|
struct Prop : Prev_Prop {};
|
|
struct State : Prev_State {
|
|
int derived{};
|
|
bool operator==(const State&) const = default;
|
|
};
|
|
struct Private : Prev_Private {};
|
|
};
|
|
using Derived = double_buffer::Impl<Derived_Object>;
|
|
struct Lifetime_Object : double_buffer::Def<Lifetime_Object, double_buffer::Root> {
|
|
struct Prop : Prev_Prop {};
|
|
struct State : Prev_State {
|
|
bool operator==(const State&) const = default;
|
|
};
|
|
struct Private : Prev_Private {
|
|
inline static int destruction_count{}; /* 测试最终 Private 是否经公共虚析构链释放。 */
|
|
~Private() override {
|
|
++destruction_count;
|
|
}
|
|
};
|
|
};
|
|
using Lifetime = double_buffer::Impl<Lifetime_Object>;
|
|
template <typename Object_Type>
|
|
std::unique_ptr<Object_Type> build_object() {
|
|
auto result = typename Object_Type::Builder{}.build();
|
|
if (!result) std::terminate();
|
|
return std::move(result).value();
|
|
}
|
|
static_assert(!std::constructible_from<Object>);
|
|
}
|
|
TEST(object_lifetime, root_destroys_attached_private_through_virtual_base) {
|
|
Lifetime_Object::Private::destruction_count = 0;
|
|
{
|
|
auto object = build_object<Lifetime>();
|
|
EXPECT_EQ(Lifetime_Object::Private::destruction_count, 0);
|
|
}
|
|
EXPECT_EQ(Lifetime_Object::Private::destruction_count, 1);
|
|
}
|
|
TEST(object_builder, build_attaches_private_and_commits_staged_values) {
|
|
Object::Builder builder;
|
|
builder.set(&Test_Object::Prop::first, 29).set<Object_Buffer_Tag>(31);
|
|
auto result = builder.build();
|
|
ASSERT_TRUE(result.has_value());
|
|
auto object = std::move(result).value();
|
|
EXPECT_EQ(object->data_for_test().current->first, 29);
|
|
EXPECT_EQ(object->data_for_test().pending->first, 0);
|
|
EXPECT_EQ(object->current_buffer<Object_Buffer_Tag>(), 31);
|
|
EXPECT_EQ(object->pending_buffer<Object_Buffer_Tag>(), 31);
|
|
}
|
|
TEST(object_buffer, state_commits_and_keeps_incremental_baseline) {
|
|
auto object = build_object<Object>();
|
|
object->update_state<&Test_Object::State::first>(3);
|
|
object->advance();
|
|
EXPECT_EQ(object->data_for_test().state.current->first, 3);
|
|
EXPECT_EQ(object->data_for_test().state.pending->first, 3);
|
|
object->update_state<&Test_Object::State::second>(5);
|
|
object->advance();
|
|
EXPECT_EQ(object->data_for_test().state.current->first, 3);
|
|
EXPECT_EQ(object->data_for_test().state.current->second, 5);
|
|
}
|
|
TEST(object_buffer, prop_publishes_and_keeps_incremental_baseline) {
|
|
auto object = build_object<Object>();
|
|
object->set<&Test_Object::Prop::first>(17);
|
|
EXPECT_EQ(object->data_for_test().current->first, 17);
|
|
EXPECT_EQ(object->data_for_test().pending->first, 0);
|
|
object->advance();
|
|
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
|
EXPECT_EQ(object->data_for_test().current->first, 17);
|
|
object->set<&Test_Object::Prop::second>(19);
|
|
object->advance();
|
|
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
|
EXPECT_EQ(object->data_for_test().pending->second, 19);
|
|
}
|
|
TEST(state_tag, callback_publishes_only_requested_layer) {
|
|
auto object = build_object<Object>();
|
|
int calls = 0;
|
|
object->set_state_callback<Test_Object::Base_Tag>(
|
|
[&](const auto& state) {
|
|
++calls;
|
|
EXPECT_EQ(state.first, 23);
|
|
}
|
|
);
|
|
object->update_state<&Test_Object::State::first>(23);
|
|
object->advance();
|
|
EXPECT_EQ(calls, 0);
|
|
object->notify_state<Test_Object::Base_Tag>();
|
|
EXPECT_EQ(calls, 1);
|
|
}
|
|
static_assert(requires(Object& object) {
|
|
object.template access_state<Test_Object::Base_Tag>([](const auto&) {});
|
|
});
|
|
static_assert(std::same_as<
|
|
decltype(std::declval<const Object&>().template read_state<Test_Object::Base_Tag>()),
|
|
const Test_Object::State&
|
|
>);
|
|
struct Missing_State_Tag {};
|
|
static_assert(!double_buffer::detail::State_Tag_In<Missing_State_Tag, Object::States>);
|
|
TEST(state_tag, inherited_tags_remain_independently_addressable) {
|
|
auto object = build_object<Derived>();
|
|
int base_calls = 0;
|
|
int derived_calls = 0;
|
|
object->set_state_callback<Test_Object::Base_Tag>([&](const auto&) { ++base_calls; });
|
|
object->set_state_callback<Derived_Object::Base_Tag>([&](const auto&) { ++derived_calls; });
|
|
object->notify_state<Test_Object::Base_Tag>();
|
|
EXPECT_EQ(base_calls, 1);
|
|
EXPECT_EQ(derived_calls, 0);
|
|
object->notify_state<Derived_Object::Base_Tag>();
|
|
EXPECT_EQ(base_calls, 1);
|
|
EXPECT_EQ(derived_calls, 1);
|
|
}
|
|
TEST(state_tag, committed_layer_is_directly_readable_by_tag) {
|
|
auto object = build_object<Derived>();
|
|
object->update_state<&Test_Object::State::first>(41);
|
|
object->update_state<&Derived_Object::State::derived>(43);
|
|
object->advance();
|
|
EXPECT_EQ(object->read_state<Test_Object::Base_Tag>().first, 41);
|
|
EXPECT_EQ(object->read_state<Derived_Object::Base_Tag>().derived, 43);
|
|
}
|
|
static_assert(double_buffer::detail::State_Tag_In<double_buffer::Root::Base_Tag, Derived::States>);
|
|
static_assert(double_buffer::detail::State_Tag_In<Test_Object::Base_Tag, Derived::States>);
|
|
static_assert(double_buffer::detail::State_Tag_In<Derived_Object::Base_Tag, Derived::States>);
|
|
static_assert(std::same_as<
|
|
decltype(std::declval<double_buffer::State_Access<Derived::State>>().template get<Test_Object::Base_Tag>()),
|
|
Test_Object::State&
|
|
>);
|
|
static_assert(std::same_as<
|
|
decltype(std::declval<double_buffer::State_Access<const Derived::State>>().template get<Derived_Object::Base_Tag>()),
|
|
const Derived_Object::State&
|
|
>);
|
|
TEST(state_tag, state_access_selects_mutable_and_const_layers_by_tag) {
|
|
Derived::State state;
|
|
double_buffer::State_Access states{state};
|
|
states.get<Test_Object::Base_Tag>().first = 31;
|
|
states.get<Derived_Object::Base_Tag>().derived = 47;
|
|
double_buffer::State_Access<Test_Object::State> base_states = states;
|
|
EXPECT_EQ(base_states.get<Test_Object::Base_Tag>().first, 31);
|
|
const auto& const_state = state;
|
|
double_buffer::State_Access current_states{const_state};
|
|
EXPECT_EQ(current_states.get<Test_Object::Base_Tag>().first, 31);
|
|
EXPECT_EQ(current_states.get<Derived_Object::Base_Tag>().derived, 47);
|
|
}
|
|
TEST(state_tag, state_chain_keeps_default_equality_usable) {
|
|
Test_Object::State first;
|
|
Test_Object::State second;
|
|
EXPECT_TRUE(first == second);
|
|
second.first = 1;
|
|
EXPECT_FALSE(first == second);
|
|
}
|
|
static_assert(std::same_as<
|
|
decltype(std::declval<double_buffer::Prop_Access<Derived::Prop>>().template get<Test_Object::Base_Tag>()),
|
|
Test_Object::Prop&
|
|
>);
|
|
static_assert(std::same_as<
|
|
decltype(std::declval<double_buffer::Private_Access<Derived_Object::Private>>().template get<Test_Object::Base_Tag>()),
|
|
Test_Object::Private&
|
|
>);
|
|
TEST(base_tag, prop_state_and_private_domains_are_independently_addressable) {
|
|
Derived::Prop prop;
|
|
double_buffer::Prop_Access props{prop};
|
|
props.get<Test_Object::Base_Tag>().first = 53;
|
|
EXPECT_EQ(props.get<Test_Object::Base_Tag>().first, 53);
|
|
Derived_Object::Private private_data;
|
|
double_buffer::Private_Access private_layers{private_data};
|
|
EXPECT_EQ(&private_layers.get<Test_Object::Base_Tag>(), static_cast<Test_Object::Private*>(&private_data));
|
|
EXPECT_EQ(&private_layers.get<Derived_Object::Base_Tag>(), static_cast<Derived_Object::Private*>(&private_data));
|
|
}
|