#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( defaults(external_access, persistence_access), synchronization(sync_all_independent, sync_group < &Device::min_speed, &Device::max_speed > ("speed_range"), sync_unsynchronized<&Device::immutable_id>()), 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" >, external_access, persistence_access) ); } }; struct Computed_Device : Property_Object { int min_speed{10}; int max_speed{100}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( defaults(external_access), 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" >), computed_property([](const auto& view) { return view.template get<&Computed_Device::max_speed>() - view.template get<&Computed_Device::min_speed>(); }, key < "speed_span" >, external_access) ); } }; struct Non_Copyable_Device : Property_Object { std::unique_ptr payload{std::make_unique(42)}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( defaults(external_access), 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" >) ); } }; template concept Has_Write_Temperature = requires(View view) { view.template write<&Device::temperature>(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<&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); const auto& schema = type_descriptor(); using Schema = type_descriptor_schema_t; static_assert(Valid_Property_Schema); static_assert(Schema::property_count == 5); 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.temperature == 20); device.temperature = 21; REQUIRE(device.read<&Device::temperature>() == 21); device.write < &Device::temperature > (22); REQUIRE(device.temperature == 22); 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>()); REQUIRE(device.lock_slot<&Device::immutable_id>() == Resolved_Synchronization_View::unsynchronized_slot); Device shared_device{property_synchronization(synchronization(sync_all_shared))}; 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>()); 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); device.external().write < &Device::temperature > (30); REQUIRE(device.external().read<&Device::temperature>() == 30); device.persistence().load < &Device::pressure > (101); REQUIRE(device.persistence().store<&Device::pressure>() == 101); 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.external().for_each_readable_locked([&](auto, const auto&, const auto&) { ++value_visits; }); REQUIRE(value_visits == 5); Computed_Device computed; REQUIRE(computed.external().read_key<"speed_span">() == 90); computed.write < &Computed_Device::min_speed > (20); REQUIRE(computed.external().read_key<"speed_span">() == 80); 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.external().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(Managed_Access_Mode::external, "temperature", &runtime_value, &runtime_read_int) == Runtime_Access_Result::ok); REQUIRE(runtime_value == 30); int runtime_write_value = 35; REQUIRE(erased.runtime_write(Managed_Access_Mode::external, "temperature", typeid(int), &runtime_write_value) == Runtime_Access_Result::ok); REQUIRE(device.temperature == 35); REQUIRE(erased.runtime_write(Managed_Access_Mode::external, "immutable_id", typeid(int), &runtime_write_value) == Runtime_Access_Result::not_writable); double wrong_type = 1.0; REQUIRE(erased.runtime_write(Managed_Access_Mode::external, "temperature", typeid(double), &wrong_type) == Runtime_Access_Result::type_mismatch); REQUIRE(erased.runtime_write(Managed_Access_Mode::external, "missing", typeid(int), &runtime_write_value) == Runtime_Access_Result::unknown_property); const Device& const_device = device; static_assert(!Has_Write_Temperature); 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(Managed_Access_Mode::external, "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))); } 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; }