#include #include "test.hpp" #include #include #include using namespace structive; struct Test_Tag_Category {}; template 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 inline constexpr Test_Tag_Attribute test_tag{}; struct Test_Multi_Category {}; template 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 inline constexpr Test_Multi_Attribute test_multi{}; struct Device : Property_Object { 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 { static auto get() { return object( 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() = 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 { static auto get() { return object( 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(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(depends_on_keys<"speed_span">, [](const auto& view) { return view.template get<"speed_span">() * 2; }, key<"double_span">), computed_property(depends_on<&Computed_Device::fixed_offset>, [](const auto& view) { return view.template get<&Computed_Device::fixed_offset>() * 2; }, key<"fixed_twice">), computed_property(depends_on<>, [](const auto&) { return 42; }, key<"constant">) ); } }; struct Non_Copyable_Device : Property_Object { std::unique_ptr payload{std::make_unique(42)}; }; template <> struct structive::Type_Descriptor { static auto get() { return object(field<&Non_Copyable_Device::payload>(key<"payload">)); } }; struct Lockless_Device : Property_Object { 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 { static auto get() { return object( synchronization(sync_all_independent, sync_group("sum", "left", "right")), field<&Lockless_Device::left>(key<"left">), field<&Lockless_Device::right>(key<"right">), computed_property(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 { int id{11}; int version{3}; int value{9}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( 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 {}; template <> struct structive::Type_Descriptor { static auto get() { return object(); } }; struct Pure_Read_Only_Device : Property_Object { int id{21}; int version{4}; }; struct Callable_Device { int value{4}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( 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 concept Can_Read_Multi_Attribute_As_Single = requires(const Property& property_value) { property_value.template attribute(); }; template concept Can_Write_Immutable = requires(Object& object) { object.template write<&Device::immutable_id>(1); }; template concept Can_Lock_Immutable_Unique = requires(Object& object) { object.template lock_unique<&Device::immutable_id>(); }; template concept Can_Write_Immutable_By_Key = requires(Object& object) { object.template write<"immutable_id">(1); }; using Device_Write_Guard = decltype(std::declval().template lock_unique<&Device::temperature>()); template concept Can_Guard_Set_Immutable = requires(Guard& guard) { guard.template set<&Device::immutable_id>(1); }; template 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(context) = *static_cast(value); } int main() { auto callable_accessor = trusted_callable_accessor( [](const Callable_Device& value) { return value.value; }, [](Callable_Device& target, int value) { target.value = value; }); static_assert(Property_Accessor); Callable_Device callable_device; REQUIRE(callable_accessor.read(callable_device) == 4); callable_accessor.write(callable_device, 9); REQUIRE(callable_device.value == 9); static_assert(Property_Described_Object); static_assert(!Can_Write_Immutable); static_assert(!Can_Write_Immutable_By_Key); static_assert(!Can_Lock_Immutable_Unique); static_assert(!Can_Guard_Set_Immutable); static_assert(!Can_Guard_Set_Immutable_By_Key); 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(); using Schema = type_descriptor_schema_t; static_assert(Valid_Property_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())>; static_assert(Temperature_Property::template has_attribute); static_assert(Temperature_Property::template attribute_count == 1); static_assert(Temperature_Property::template attribute_count == 2); static_assert(!Can_Read_Multi_Attribute_As_Single); REQUIRE(Temperature_Property::template attribute_type::value == 7); int multi_sum = 0; schema.template property<&Device::temperature>().template for_each_attribute([&](const auto& attribute) { multi_sum += std::remove_cvref_t::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(); static_assert(type_descriptor_schema_t::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(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; constexpr auto speed_span_index = schema_property_index_v; constexpr auto speed_span_dependencies = schema_property_dependency_indices(); static_assert(speed_span_dependencies.size() == 3); static_assert(speed_span_dependencies[0] == schema_member_property_index_v); static_assert(speed_span_dependencies[1] == schema_member_property_index_v); static_assert(speed_span_dependencies[2] == schema_member_property_index_v); 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; constexpr auto fixed_twice_index = schema_property_index_v; constexpr auto constant_index = schema_property_index_v; static_assert(schema_dependency_graph_acyclic()); static_assert(schema_property_dependency_count_v == 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(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; }