269 lines
13 KiB
C++
269 lines
13 KiB
C++
#include <structive/property/property.hpp>
|
|
#include <atomic>
|
|
#include <barrier>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <semaphore>
|
|
#include <string>
|
|
#include <thread>
|
|
#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{};
|
|
#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> {
|
|
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>(
|
|
defaults(external_access<External_Access::read_write>, persistence_access<Persistence_Access::load_store>),
|
|
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<External_Access::read>, persistence_access<Persistence_Access::store>)
|
|
);
|
|
}
|
|
};
|
|
struct Computed_Device : Property_Object<Computed_Device> {
|
|
int min_speed{10};
|
|
int max_speed{100};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Computed_Device> {
|
|
static auto get() {
|
|
return object<Computed_Device>(
|
|
defaults(external_access<External_Access::read_write>),
|
|
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<Computed_Device, int>([](const auto& view) {
|
|
return view.template get<&Computed_Device::max_speed>() - view.template get<&Computed_Device::min_speed>();
|
|
}, key < "speed_span" >, external_access<External_Access::read>)
|
|
);
|
|
}
|
|
};
|
|
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>(
|
|
defaults(external_access<External_Access::read>),
|
|
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", "sum")),
|
|
field < &Lockless_Device::left > (key < "left" >),
|
|
field < &Lockless_Device::right > (key < "right" >),
|
|
computed_property<Lockless_Device, int>([](const auto& view) {
|
|
return view.template get<&Lockless_Device::left>() + view.template get<&Lockless_Device::right>();
|
|
}, key < "sum" >)
|
|
);
|
|
}
|
|
};
|
|
template <class View>
|
|
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<int*>(context) = *static_cast<const int*>(value);
|
|
}
|
|
int main() {
|
|
static_assert(Property_Described_Object<Device>);
|
|
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);
|
|
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>);
|
|
REQUIRE(Temperature_Property::template attribute_type<Test_Tag_Category>::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<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);
|
|
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("temperature", &runtime_value, &runtime_read_int) == Runtime_Access_Result::ok);
|
|
REQUIRE(runtime_value == 30);
|
|
int intrinsic_runtime_write_value = 34;
|
|
REQUIRE(erased.runtime_write("immutable_id", typeid(int), &intrinsic_runtime_write_value) == Runtime_Access_Result::ok);
|
|
REQUIRE(device.immutable_id == 34);
|
|
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);
|
|
int persistence_write_value = 102;
|
|
REQUIRE(erased.runtime_write(Managed_Access_Mode::persistence, "pressure", typeid(int), &persistence_write_value) == Runtime_Access_Result::ok);
|
|
REQUIRE(device.pressure == 102);
|
|
REQUIRE(erased.runtime_read(Managed_Access_Mode::persistence, "immutable_id", &runtime_value, &runtime_read_int) == Runtime_Access_Result::ok);
|
|
REQUIRE(runtime_value == 34);
|
|
REQUIRE(erased.runtime_write(Managed_Access_Mode::persistence, "immutable_id", typeid(int), &persistence_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<decltype(const_device.external())>);
|
|
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)));
|
|
}
|
|
std::barrier gate(2);
|
|
std::atomic<int> 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;
|
|
}
|