#include #include #include #include #include #include #include #include #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{}; #define REQUIRE(expression) do { if (!(expression)) { std::fprintf(stderr, "REQUIRE failed: %s:%d: %s\n", __FILE__, __LINE__, #expression); std::abort(); } } while (false) 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>), 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 { 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", "speed_span")), 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([](const auto& view) { return view.template get<&Computed_Device::max_speed>() - view.template get<&Computed_Device::min_speed>() + view.template get<&Computed_Device::fixed_offset>(); }, key<"speed_span">) ); } }; 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", "sum")), field<&Lockless_Device::left>(key<"left">), field<&Lockless_Device::right>(key<"right">), computed_property([](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 Pure_Read_Only_Device : Property_Object { int id{21}; int version{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_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>(); }; 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<&Device::min_speed>(); int old_max = guard.get<&Device::max_speed>(); guard.set<&Device::min_speed>(min_speed); guard.set<&Device::max_speed>(max_speed); if (min_speed <= max_speed) { return true; } guard.set<&Device::min_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() { static_assert(Property_Described_Object); static_assert(!Can_Write_Immutable); static_assert(!Can_Lock_Immutable_Unique); 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); REQUIRE(Temperature_Property::template attribute_type::value == 7); 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<&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"); 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); Computed_Device computed; REQUIRE(computed.lock_slot<&Computed_Device::fixed_offset>() == Resolved_Synchronization_View::unsynchronized_slot); REQUIRE(computed.read_key<"speed_span">() == 95); computed.write<&Computed_Device::min_speed>(20); REQUIRE(computed.read_key<"speed_span">() == 85); Lockless_Device lockless; REQUIRE(lockless.read<&Lockless_Device::left>() == 1); lockless.write<&Lockless_Device::right>(4); REQUIRE(lockless.read_key<"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); Property_Object_Base& erased = device; REQUIRE(erased.runtime_object_type() == typeid(Device)); REQUIRE(erased.runtime_property_count() == 5); int runtime_value = 0; REQUIRE(erased.runtime_read("temperature", &runtime_value, &runtime_read_int) == Runtime_Access_Result::ok); REQUIRE(runtime_value == 22); REQUIRE(erased.runtime_read("immutable_id", &runtime_value, &runtime_read_int) == Runtime_Access_Result::ok); REQUIRE(runtime_value == 7); int runtime_write_value = 35; REQUIRE(erased.runtime_write("temperature", typeid(int), &runtime_write_value) == Runtime_Access_Result::ok); REQUIRE(device.temperature == 35); REQUIRE(erased.runtime_write("immutable_id", typeid(int), &runtime_write_value) == Runtime_Access_Result::not_writable); double wrong_type = 1.0; REQUIRE(erased.runtime_write("temperature", typeid(double), &wrong_type) == Runtime_Access_Result::type_mismatch); REQUIRE(erased.runtime_write("missing", typeid(int), &runtime_write_value) == Runtime_Access_Result::unknown_property); 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); Property_Object_Base& read_only_erased = read_only_device; std::binary_semaphore read_only_done{0}; std::jthread read_only_reader; { auto guard = read_only_device.lock_unique<&Read_Only_Device::value>(); read_only_reader = std::jthread([&] { int value = 0; REQUIRE(read_only_erased.runtime_read("id", &value, &runtime_read_int) == Runtime_Access_Result::ok); REQUIRE(value == 11); read_only_done.release(); }); REQUIRE(read_only_done.try_acquire_for(std::chrono::milliseconds(100))); } read_only_reader.join(); std::binary_semaphore runtime_blocked_done{0}; std::jthread runtime_writer; { auto guard = device.lock_unique<&Device::temperature>(); runtime_writer = std::jthread([&] { int value = 36; REQUIRE(erased.runtime_write("temperature", typeid(int), &value) == Runtime_Access_Result::ok); runtime_blocked_done.release(); }); REQUIRE(!runtime_blocked_done.try_acquire_for(std::chrono::milliseconds(20))); } REQUIRE(runtime_blocked_done.try_acquire_for(std::chrono::seconds(2))); runtime_writer.join(); REQUIRE(device.read<&Device::temperature>() == 36); std::binary_semaphore independent_done{0}; { auto guard = device.lock_unique({"temperature"}); std::jthread writer([&] { device.write<&Device::pressure>(200); independent_done.release(); }); REQUIRE(independent_done.try_acquire_for(std::chrono::seconds(2))); } bool dynamic_read_only_unique_rejected = false; try { auto guard = device.lock_unique({"immutable_id"}); (void) guard; } catch (const std::invalid_argument&) { dynamic_read_only_unique_rejected = true; } REQUIRE(dynamic_read_only_unique_rejected); std::barrier gate(2); std::atomic completed{0}; std::jthread first([&] { gate.arrive_and_wait(); auto guard = device.lock_unique({"temperature", "pressure"}); guard.set<&Device::temperature>(40); guard.set<&Device::pressure>(140); completed.fetch_add(1, std::memory_order_release); }); std::jthread second([&] { gate.arrive_and_wait(); auto guard = device.lock_unique({"pressure", "temperature"}); guard.set<&Device::pressure>(141); guard.set<&Device::temperature>(41); completed.fetch_add(1, std::memory_order_release); }); first.join(); second.join(); REQUIRE(completed.load(std::memory_order_acquire) == 2); Device copied = device; REQUIRE(copied.temperature == device.temperature); copied.write<&Device::temperature>(99); REQUIRE(device.read<&Device::temperature>() != copied.read<&Device::temperature>()); return 0; }