358 lines
18 KiB
C++
358 lines
18 KiB
C++
#include <structive/property/property.hpp>
|
|
#include "test.hpp"
|
|
#include <memory>
|
|
#include <string>
|
|
#include <type_traits>
|
|
using namespace structive;
|
|
struct Test_Tag_Category {};
|
|
template <int Value>
|
|
struct Test_Tag_Attribute {
|
|
using attribute_category = Test_Tag_Category;
|
|
static constexpr bool single_valued = true;
|
|
static constexpr bool inheritable = false;
|
|
static constexpr int value = Value;
|
|
};
|
|
template <int Value>
|
|
inline constexpr Test_Tag_Attribute<Value> test_tag{};
|
|
struct Test_Multi_Category {};
|
|
template <int Value>
|
|
struct Test_Multi_Attribute {
|
|
using attribute_category = Test_Multi_Category;
|
|
static constexpr bool single_valued = false;
|
|
static constexpr bool inheritable = false;
|
|
static constexpr int value = Value;
|
|
};
|
|
template <int Value>
|
|
inline constexpr Test_Multi_Attribute<Value> test_multi{};
|
|
struct Device : Property_Object<Device> {
|
|
Device() = default;
|
|
explicit Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {}
|
|
int temperature{20};
|
|
int pressure{100};
|
|
int min_speed{10};
|
|
int max_speed{100};
|
|
int immutable_id{7};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Device> {
|
|
static auto get() {
|
|
return object<Device>(
|
|
synchronization(sync_all_independent, sync_group<&Device::min_speed, &Device::max_speed>("speed_range")),
|
|
field<&Device::temperature>(key<"temperature">, min_value<-50>, max_value<200>, unit<"C">, test_tag<7>, test_multi<3>, test_multi<5>),
|
|
field<&Device::pressure>(key<"pressure">),
|
|
field<&Device::min_speed>(key<"minimum_speed">),
|
|
field<&Device::max_speed>(key<"maximum_speed">),
|
|
field<&Device::immutable_id>(key<"immutable_id">, read_only)
|
|
);
|
|
}
|
|
};
|
|
struct Computed_Device : Property_Object<Computed_Device> {
|
|
Computed_Device() = default;
|
|
explicit Computed_Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {}
|
|
int min_speed{10};
|
|
int max_speed{100};
|
|
int fixed_offset{5};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Computed_Device> {
|
|
static auto get() {
|
|
return object<Computed_Device>(
|
|
synchronization(sync_all_independent, sync_group("speed", "min_speed", "max_speed")),
|
|
field<&Computed_Device::min_speed>(key<"min_speed">),
|
|
field<&Computed_Device::max_speed>(key<"max_speed">),
|
|
field<&Computed_Device::fixed_offset>(key<"fixed_offset">, read_only),
|
|
computed_property<Computed_Device, int>(depends_on<&Computed_Device::min_speed, &Computed_Device::max_speed, &Computed_Device::fixed_offset>, [](const auto& view) {
|
|
return view.template get<"max_speed">() - view.template get<"min_speed">() + view.template get<"fixed_offset">();
|
|
}, key<"speed_span">),
|
|
computed_property<Computed_Device, int>(depends_on_keys<"speed_span">, [](const auto& view) {
|
|
return view.template get<"speed_span">() * 2;
|
|
}, key<"double_span">),
|
|
computed_property<Computed_Device, int>(depends_on<&Computed_Device::fixed_offset>, [](const auto& view) {
|
|
return view.template get<&Computed_Device::fixed_offset>() * 2;
|
|
}, key<"fixed_twice">),
|
|
computed_property<Computed_Device, int>(depends_on<>, [](const auto&) {
|
|
return 42;
|
|
}, key<"constant">)
|
|
);
|
|
}
|
|
};
|
|
struct Non_Copyable_Device : Property_Object<Non_Copyable_Device> {
|
|
std::unique_ptr<int> payload{std::make_unique<int>(42)};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Non_Copyable_Device> {
|
|
static auto get() {
|
|
return object<Non_Copyable_Device>(field<&Non_Copyable_Device::payload>(key<"payload">));
|
|
}
|
|
};
|
|
struct Lockless_Device : Property_Object<Lockless_Device, No_Lock_Policy> {
|
|
Lockless_Device() = default;
|
|
explicit Lockless_Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {}
|
|
int left{1};
|
|
int right{2};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Lockless_Device> {
|
|
static auto get() {
|
|
return object<Lockless_Device>(
|
|
synchronization(sync_all_independent, sync_group("sum", "left", "right")),
|
|
field<&Lockless_Device::left>(key<"left">),
|
|
field<&Lockless_Device::right>(key<"right">),
|
|
computed_property<Lockless_Device, int>(depends_on_keys<"left", "right">, [](const auto& view) {
|
|
return view.template get<&Lockless_Device::left>() + view.template get<&Lockless_Device::right>();
|
|
}, key<"sum">)
|
|
);
|
|
}
|
|
};
|
|
struct Read_Only_Device : Property_Object<Read_Only_Device> {
|
|
int id{11};
|
|
int version{3};
|
|
int value{9};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Read_Only_Device> {
|
|
static auto get() {
|
|
return object<Read_Only_Device>(
|
|
synchronization(sync_all_shared),
|
|
field<&Read_Only_Device::id>(key<"id">, read_only),
|
|
field<&Read_Only_Device::version>(key<"version">, read_only),
|
|
field<&Read_Only_Device::value>(key<"value">)
|
|
);
|
|
}
|
|
};
|
|
struct Empty_Device : Property_Object<Empty_Device> {};
|
|
template <>
|
|
struct structive::Type_Descriptor<Empty_Device> {
|
|
static auto get() {
|
|
return object<Empty_Device>();
|
|
}
|
|
};
|
|
struct Pure_Read_Only_Device : Property_Object<Pure_Read_Only_Device> {
|
|
int id{21};
|
|
int version{4};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Pure_Read_Only_Device> {
|
|
static auto get() {
|
|
return object<Pure_Read_Only_Device>(
|
|
synchronization(sync_all_shared),
|
|
field<&Pure_Read_Only_Device::id>(key<"id">, read_only),
|
|
field<&Pure_Read_Only_Device::version>(key<"version">, read_only)
|
|
);
|
|
}
|
|
};
|
|
template <class Property>
|
|
concept Can_Read_Multi_Attribute_As_Single = requires(const Property& property_value) {
|
|
property_value.template attribute<Test_Multi_Category>();
|
|
};
|
|
template <class Object>
|
|
concept Can_Write_Immutable = requires(Object& object) {
|
|
object.template write<&Device::immutable_id>(1);
|
|
};
|
|
template <class Object>
|
|
concept Can_Lock_Immutable_Unique = requires(Object& object) {
|
|
object.template lock_unique<&Device::immutable_id>();
|
|
};
|
|
template <class Object>
|
|
concept Can_Write_Immutable_By_Key = requires(Object& object) {
|
|
object.template write<"immutable_id">(1);
|
|
};
|
|
using Device_Write_Guard = decltype(std::declval<Device&>().template lock_unique<&Device::temperature>());
|
|
template <class Guard>
|
|
concept Can_Guard_Set_Immutable = requires(Guard& guard) {
|
|
guard.template set<&Device::immutable_id>(1);
|
|
};
|
|
template <class Guard>
|
|
concept Can_Guard_Set_Immutable_By_Key = requires(Guard& guard) {
|
|
guard.template set<"immutable_id">(1);
|
|
};
|
|
static bool update_speed_range(Device& device, int min_speed, int max_speed) {
|
|
auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>();
|
|
int old_min = guard.get<"minimum_speed">();
|
|
int old_max = guard.get<&Device::max_speed>();
|
|
guard.set<"minimum_speed">(min_speed);
|
|
guard.set<&Device::max_speed>(max_speed);
|
|
if (min_speed <= max_speed) {
|
|
return true;
|
|
}
|
|
guard.set<"minimum_speed">(old_min);
|
|
guard.set<&Device::max_speed>(old_max);
|
|
return false;
|
|
}
|
|
static void runtime_read_int(void* context, std::size_t, std::string_view, const std::type_info& type, const void* value) {
|
|
REQUIRE(type == typeid(int));
|
|
*static_cast<int*>(context) = *static_cast<const int*>(value);
|
|
}
|
|
int main() {
|
|
static_assert(Property_Described_Object<Device>);
|
|
static_assert(!Can_Write_Immutable<Device>);
|
|
static_assert(!Can_Write_Immutable_By_Key<Device>);
|
|
static_assert(!Can_Lock_Immutable_Unique<Device>);
|
|
static_assert(!Can_Guard_Set_Immutable<Device_Write_Guard>);
|
|
static_assert(!Can_Guard_Set_Immutable_By_Key<Device_Write_Guard>);
|
|
static_assert(requires(const Device& value) { value.template read<"temperature">(); });
|
|
static_assert(requires(Device& value) { value.template write<"temperature">(1); });
|
|
const auto& schema = type_descriptor<Device>();
|
|
using Schema = type_descriptor_schema_t<Device>;
|
|
static_assert(Valid_Property_Schema<Schema>);
|
|
static_assert(Schema::property_count == 5);
|
|
using Immutable_Property = typename Schema::template property_type<4>;
|
|
static_assert(Immutable_Property::readable);
|
|
static_assert(!Immutable_Property::writable);
|
|
REQUIRE(schema.template property<0>().key() == "temperature");
|
|
REQUIRE(schema.template property<&Device::temperature>().key() == "temperature");
|
|
REQUIRE(schema.template property<&Device::min_speed>().key() == "minimum_speed");
|
|
REQUIRE(schema.template property<&Device::max_speed>().key() == "maximum_speed");
|
|
using Temperature_Property = std::remove_cvref_t<decltype(schema.template property<&Device::temperature>())>;
|
|
static_assert(Temperature_Property::template has_attribute<Test_Tag_Category>);
|
|
static_assert(Temperature_Property::template attribute_count<Test_Tag_Category> == 1);
|
|
static_assert(Temperature_Property::template attribute_count<Test_Multi_Category> == 2);
|
|
static_assert(!Can_Read_Multi_Attribute_As_Single<Temperature_Property>);
|
|
REQUIRE(Temperature_Property::template attribute_type<Test_Tag_Category>::value == 7);
|
|
int multi_sum = 0;
|
|
schema.template property<&Device::temperature>().template for_each_attribute<Test_Multi_Category>([&](const auto& attribute) {
|
|
multi_sum += std::remove_cvref_t<decltype(attribute)>::value;
|
|
});
|
|
REQUIRE(multi_sum == 8);
|
|
std::size_t metadata_count = 0;
|
|
std::size_t attribute_count = 0;
|
|
std::size_t constraint_count = 0;
|
|
schema.template property<&Device::temperature>().for_each_metadata([&](const auto&) {
|
|
++metadata_count;
|
|
});
|
|
schema.template property<&Device::temperature>().for_each_attribute([&](const auto&) {
|
|
++attribute_count;
|
|
});
|
|
schema.template property<&Device::temperature>().for_each_constraint([&](const auto&) {
|
|
++constraint_count;
|
|
});
|
|
REQUIRE(metadata_count == 7);
|
|
REQUIRE(attribute_count == 5);
|
|
REQUIRE(constraint_count == 2);
|
|
const auto& empty_schema = type_descriptor<Empty_Device>();
|
|
static_assert(type_descriptor_schema_t<Empty_Device>::property_count == 0);
|
|
REQUIRE(empty_schema.synchronization_plan().property_rules().empty());
|
|
Empty_Device empty_device;
|
|
REQUIRE(empty_device.resolved_synchronization().lock_count == 0);
|
|
Property_Object_Base& empty_erased = empty_device;
|
|
REQUIRE(empty_erased.runtime_property_count() == 0);
|
|
int empty_value = 0;
|
|
REQUIRE(empty_erased.runtime_read("missing", &empty_value, &runtime_read_int) == Runtime_Access_Result::unknown_property);
|
|
Device device;
|
|
REQUIRE(device.resolved_synchronization().lock_count == 3);
|
|
REQUIRE(device.lock_slot<&Device::immutable_id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
REQUIRE(device.temperature == 20);
|
|
device.temperature = 21;
|
|
REQUIRE(device.read<&Device::temperature>() == 21);
|
|
device.write<&Device::temperature>(22);
|
|
REQUIRE(device.temperature == 22);
|
|
REQUIRE(device.read<"temperature">() == 22);
|
|
device.write<"temperature">(23);
|
|
REQUIRE(device.temperature == 23);
|
|
REQUIRE(device.read<&Device::immutable_id>() == 7);
|
|
REQUIRE(&device.unsafe_object() == &device);
|
|
REQUIRE(&device.schema() == &schema);
|
|
REQUIRE(device.lock_slot<&Device::min_speed>() == device.lock_slot<&Device::max_speed>());
|
|
REQUIRE(device.lock_slot<&Device::temperature>() != device.lock_slot<&Device::pressure>());
|
|
Device shared_device{property_synchronization(synchronization(sync_all_shared))};
|
|
REQUIRE(shared_device.resolved_synchronization().lock_count == 1);
|
|
REQUIRE(shared_device.lock_slot<&Device::temperature>() == shared_device.lock_slot<&Device::pressure>());
|
|
REQUIRE(shared_device.lock_slot<&Device::pressure>() == shared_device.lock_slot<&Device::min_speed>());
|
|
REQUIRE(shared_device.lock_slot<&Device::immutable_id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
Device grouped_device{property_synchronization<Device>(synchronization(sync_all_independent, sync_group<&Device::temperature, &Device::pressure>("environment")))};
|
|
REQUIRE(grouped_device.lock_slot<&Device::temperature>() == grouped_device.lock_slot<&Device::pressure>());
|
|
Device shared_copy{shared_device};
|
|
REQUIRE(shared_copy.lock_slot<&Device::temperature>() == shared_copy.lock_slot<&Device::pressure>());
|
|
Device shared_move{std::move(shared_copy)};
|
|
REQUIRE(shared_move.lock_slot<&Device::temperature>() == shared_move.lock_slot<&Device::pressure>());
|
|
Device assignment_target;
|
|
auto assignment_target_slot = assignment_target.lock_slot<&Device::pressure>();
|
|
assignment_target = shared_device;
|
|
REQUIRE(assignment_target.lock_slot<&Device::pressure>() == assignment_target_slot);
|
|
auto validation = validate_property_value<&Device::temperature>(device.schema(), 500);
|
|
REQUIRE(validation.has_value());
|
|
REQUIRE(validation->code == "max_value");
|
|
device.write<&Device::temperature>(500);
|
|
REQUIRE(device.read<&Device::temperature>() == 500);
|
|
device.write<&Device::temperature>(23);
|
|
REQUIRE(!update_speed_range(device, 200, 100));
|
|
REQUIRE(device.read<&Device::min_speed>() == 10);
|
|
REQUIRE(device.read<&Device::max_speed>() == 100);
|
|
REQUIRE(update_speed_range(device, 20, 120));
|
|
REQUIRE(device.read<&Device::min_speed>() == 20);
|
|
REQUIRE(device.read<&Device::max_speed>() == 120);
|
|
std::size_t schema_visits = 0;
|
|
schema.for_each_property([&](auto, const auto&) {
|
|
++schema_visits;
|
|
});
|
|
REQUIRE(schema_visits == 5);
|
|
std::size_t value_visits = 0;
|
|
device.for_each_readable_locked([&](auto, const auto&, const auto&) {
|
|
++value_visits;
|
|
});
|
|
REQUIRE(value_visits == 5);
|
|
using Computed_Schema = type_descriptor_schema_t<Computed_Device>;
|
|
constexpr auto speed_span_index = schema_property_index_v<Computed_Schema, "speed_span">;
|
|
constexpr auto speed_span_dependencies = schema_property_dependency_indices<Computed_Schema, speed_span_index>();
|
|
static_assert(speed_span_dependencies.size() == 3);
|
|
static_assert(speed_span_dependencies[0] == schema_member_property_index_v<Computed_Schema, &Computed_Device::min_speed>);
|
|
static_assert(speed_span_dependencies[1] == schema_member_property_index_v<Computed_Schema, &Computed_Device::max_speed>);
|
|
static_assert(speed_span_dependencies[2] == schema_member_property_index_v<Computed_Schema, &Computed_Device::fixed_offset>);
|
|
Computed_Device computed;
|
|
REQUIRE(computed.lock_slot<&Computed_Device::fixed_offset>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
constexpr auto double_span_index = schema_property_index_v<Computed_Schema, "double_span">;
|
|
constexpr auto fixed_twice_index = schema_property_index_v<Computed_Schema, "fixed_twice">;
|
|
constexpr auto constant_index = schema_property_index_v<Computed_Schema, "constant">;
|
|
static_assert(schema_dependency_graph_acyclic<Computed_Schema>());
|
|
static_assert(schema_property_dependency_count_v<Computed_Schema, constant_index> == 0);
|
|
REQUIRE(computed.resolved_synchronization().slot(speed_span_index) == computed.lock_slot<&Computed_Device::min_speed>());
|
|
REQUIRE(computed.resolved_synchronization().slot(speed_span_index) == computed.lock_slot<&Computed_Device::max_speed>());
|
|
REQUIRE(computed.resolved_synchronization().slot(double_span_index) == computed.resolved_synchronization().slot(speed_span_index));
|
|
REQUIRE(computed.resolved_synchronization().slot(fixed_twice_index) == Resolved_Synchronization_View::unsynchronized_slot);
|
|
REQUIRE(computed.resolved_synchronization().slot(constant_index) == Resolved_Synchronization_View::unsynchronized_slot);
|
|
REQUIRE(computed.read<"speed_span">() == 95);
|
|
REQUIRE(computed.read<"double_span">() == 190);
|
|
REQUIRE(computed.read<"fixed_twice">() == 10);
|
|
REQUIRE(computed.read<"constant">() == 42);
|
|
computed.write<&Computed_Device::min_speed>(20);
|
|
REQUIRE(computed.read<"speed_span">() == 85);
|
|
REQUIRE(computed.read<"double_span">() == 170);
|
|
bool rejected_independent_dependencies = false;
|
|
try {
|
|
Computed_Device invalid{property_synchronization<Computed_Device>(synchronization(sync_all_independent))};
|
|
} catch (const std::invalid_argument&) {
|
|
rejected_independent_dependencies = true;
|
|
}
|
|
REQUIRE(rejected_independent_dependencies);
|
|
Lockless_Device lockless;
|
|
REQUIRE(lockless.read<&Lockless_Device::left>() == 1);
|
|
lockless.write<&Lockless_Device::right>(4);
|
|
REQUIRE(lockless.read<"sum">() == 5);
|
|
auto lockless_guard = lockless.lock_shared<&Lockless_Device::left, &Lockless_Device::right>();
|
|
REQUIRE(lockless_guard.get<&Lockless_Device::left>() == 1);
|
|
REQUIRE(lockless_guard.get<&Lockless_Device::right>() == 4);
|
|
Non_Copyable_Device non_copyable;
|
|
bool non_copyable_visited = false;
|
|
non_copyable.for_each_readable_locked([&](auto, const auto&, const auto& value) {
|
|
REQUIRE(*value == 42);
|
|
non_copyable_visited = true;
|
|
});
|
|
REQUIRE(non_copyable_visited);
|
|
Read_Only_Device read_only_device;
|
|
REQUIRE(read_only_device.resolved_synchronization().lock_count == 1);
|
|
REQUIRE(read_only_device.lock_slot<&Read_Only_Device::id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
REQUIRE(read_only_device.lock_slot<&Read_Only_Device::version>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
REQUIRE(read_only_device.lock_slot<&Read_Only_Device::value>() == 0);
|
|
Pure_Read_Only_Device pure_read_only;
|
|
REQUIRE(pure_read_only.resolved_synchronization().lock_count == 0);
|
|
REQUIRE(pure_read_only.lock_slot<&Pure_Read_Only_Device::id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
REQUIRE(pure_read_only.lock_slot<&Pure_Read_Only_Device::version>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
REQUIRE(pure_read_only.read<&Pure_Read_Only_Device::id>() == 21);
|
|
REQUIRE(pure_read_only.read<&Pure_Read_Only_Device::version>() == 4);
|
|
Device copied = device;
|
|
REQUIRE(copied.temperature == device.temperature);
|
|
copied.write<&Device::temperature>(99);
|
|
REQUIRE(device.read<&Device::temperature>() != copied.read<&Device::temperature>());
|
|
return 0;
|
|
}
|