api更新
This commit is contained in:
@@ -57,7 +57,7 @@ struct structive::Type_Descriptor<Computed_Device> {
|
||||
field<&Computed_Device::max_speed>(key<"max_speed">),
|
||||
field<&Computed_Device::fixed_offset>(key<"fixed_offset">, read_only),
|
||||
computed_property<Computed_Device, int>([](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>();
|
||||
return view.template get<"max_speed">() - view.template get<"min_speed">() + view.template get<"fixed_offset">();
|
||||
}, key<"speed_span">)
|
||||
);
|
||||
}
|
||||
@@ -128,16 +128,29 @@ 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<&Device::min_speed>();
|
||||
int old_min = guard.get<"minimum_speed">();
|
||||
int old_max = guard.get<&Device::max_speed>();
|
||||
guard.set<&Device::min_speed>(min_speed);
|
||||
guard.set<"minimum_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<"minimum_speed">(old_min);
|
||||
guard.set<&Device::max_speed>(old_max);
|
||||
return false;
|
||||
}
|
||||
@@ -148,7 +161,12 @@ static void runtime_read_int(void* context, std::size_t, std::string_view, const
|
||||
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>);
|
||||
@@ -171,6 +189,9 @@ int main() {
|
||||
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);
|
||||
@@ -212,13 +233,13 @@ int main() {
|
||||
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);
|
||||
REQUIRE(computed.read<"speed_span">() == 95);
|
||||
computed.write<&Computed_Device::min_speed>(20);
|
||||
REQUIRE(computed.read_key<"speed_span">() == 85);
|
||||
REQUIRE(computed.read<"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);
|
||||
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);
|
||||
@@ -234,7 +255,7 @@ int main() {
|
||||
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(runtime_value == 23);
|
||||
REQUIRE(erased.runtime_read("immutable_id", &runtime_value, &runtime_read_int) == Runtime_Access_Result::ok);
|
||||
REQUIRE(runtime_value == 7);
|
||||
int runtime_write_value = 35;
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#include <structive/property/property.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <semaphore>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <typeinfo>
|
||||
using namespace structive;
|
||||
#define REQUIRE(expression) do { if (!(expression)) { std::fprintf(stderr, "REQUIRE failed: %s:%d: %s\n", __FILE__, __LINE__, #expression); std::abort(); } } while (false)
|
||||
struct Runtime_Device : Property_Object<Runtime_Device> {
|
||||
int value{10};
|
||||
int serial{7};
|
||||
int command{0};
|
||||
};
|
||||
template <>
|
||||
struct structive::Type_Descriptor<Runtime_Device> {
|
||||
static auto get() {
|
||||
return object<Runtime_Device>(
|
||||
synchronization(sync_all_independent),
|
||||
field<&Runtime_Device::value>(key<"value">),
|
||||
field<&Runtime_Device::serial>(key<"serial">, read_only),
|
||||
field<&Runtime_Device::command>(key<"command">, write_only)
|
||||
);
|
||||
}
|
||||
};
|
||||
struct Runtime_Read_Capture {
|
||||
std::size_t calls{};
|
||||
std::size_t index{};
|
||||
std::string key;
|
||||
const std::type_info* type{};
|
||||
int value{};
|
||||
};
|
||||
static void capture_int(void* context, std::size_t index, std::string_view key, const std::type_info& type, const void* value) {
|
||||
auto& capture = *static_cast<Runtime_Read_Capture*>(context);
|
||||
++capture.calls;
|
||||
capture.index = index;
|
||||
capture.key = key;
|
||||
capture.type = &type;
|
||||
capture.value = *static_cast<const int*>(value);
|
||||
}
|
||||
template <class Object>
|
||||
concept Can_Read_Command = requires(const Object& object) {
|
||||
object.template read<"command">();
|
||||
};
|
||||
template <class Object>
|
||||
concept Can_Write_Serial = requires(Object& object) {
|
||||
object.template write<"serial">(1);
|
||||
};
|
||||
static void test_runtime_metadata_and_results() {
|
||||
Runtime_Device device;
|
||||
Property_Object_Base& erased = device;
|
||||
static_assert(!Can_Read_Command<Runtime_Device>);
|
||||
static_assert(!Can_Write_Serial<Runtime_Device>);
|
||||
REQUIRE(erased.runtime_object_type() == typeid(Runtime_Device));
|
||||
REQUIRE(erased.runtime_property_count() == 3);
|
||||
Runtime_Read_Capture capture;
|
||||
REQUIRE(erased.runtime_read("value", &capture, &capture_int) == Runtime_Access_Result::ok);
|
||||
REQUIRE(capture.calls == 1);
|
||||
REQUIRE(capture.index == 0);
|
||||
REQUIRE(capture.key == "value");
|
||||
REQUIRE(capture.type != nullptr && *capture.type == typeid(int));
|
||||
REQUIRE(capture.value == 10);
|
||||
capture = {};
|
||||
REQUIRE(erased.runtime_read("serial", &capture, &capture_int) == Runtime_Access_Result::ok);
|
||||
REQUIRE(capture.calls == 1);
|
||||
REQUIRE(capture.index == 1);
|
||||
REQUIRE(capture.value == 7);
|
||||
capture = {};
|
||||
REQUIRE(erased.runtime_read("command", &capture, &capture_int) == Runtime_Access_Result::not_readable);
|
||||
REQUIRE(capture.calls == 0);
|
||||
REQUIRE(erased.runtime_read("missing", &capture, &capture_int) == Runtime_Access_Result::unknown_property);
|
||||
REQUIRE(capture.calls == 0);
|
||||
int value = 20;
|
||||
REQUIRE(erased.runtime_write("value", typeid(int), &value) == Runtime_Access_Result::ok);
|
||||
REQUIRE(device.value == 20);
|
||||
int command = 5;
|
||||
REQUIRE(erased.runtime_write("command", typeid(int), &command) == Runtime_Access_Result::ok);
|
||||
REQUIRE(device.command == 5);
|
||||
REQUIRE(erased.runtime_write("serial", typeid(int), &value) == Runtime_Access_Result::not_writable);
|
||||
double wrong_type = 1.0;
|
||||
REQUIRE(erased.runtime_write("value", typeid(double), &wrong_type) == Runtime_Access_Result::type_mismatch);
|
||||
REQUIRE(erased.runtime_write("missing", typeid(int), &value) == Runtime_Access_Result::unknown_property);
|
||||
}
|
||||
static void test_runtime_access_uses_managed_synchronization() {
|
||||
Runtime_Device device;
|
||||
Property_Object_Base& erased = device;
|
||||
std::binary_semaphore read_done{0};
|
||||
std::jthread reader;
|
||||
{
|
||||
auto guard = device.lock_unique<&Runtime_Device::value>();
|
||||
reader = std::jthread([&] {
|
||||
Runtime_Read_Capture capture;
|
||||
REQUIRE(erased.runtime_read("value", &capture, &capture_int) == Runtime_Access_Result::ok);
|
||||
REQUIRE(capture.value == 10);
|
||||
read_done.release();
|
||||
});
|
||||
REQUIRE(!read_done.try_acquire_for(std::chrono::milliseconds(20)));
|
||||
}
|
||||
REQUIRE(read_done.try_acquire_for(std::chrono::seconds(2)));
|
||||
reader.join();
|
||||
std::binary_semaphore write_done{0};
|
||||
std::jthread writer;
|
||||
{
|
||||
auto guard = device.lock_shared<&Runtime_Device::value>();
|
||||
writer = std::jthread([&] {
|
||||
int value = 30;
|
||||
REQUIRE(erased.runtime_write("value", typeid(int), &value) == Runtime_Access_Result::ok);
|
||||
write_done.release();
|
||||
});
|
||||
REQUIRE(!write_done.try_acquire_for(std::chrono::milliseconds(20)));
|
||||
}
|
||||
REQUIRE(write_done.try_acquire_for(std::chrono::seconds(2)));
|
||||
writer.join();
|
||||
REQUIRE(device.value == 30);
|
||||
}
|
||||
static void test_runtime_read_only_fast_path() {
|
||||
Runtime_Device device;
|
||||
Property_Object_Base& erased = device;
|
||||
REQUIRE(device.lock_slot<&Runtime_Device::serial>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
std::binary_semaphore done{0};
|
||||
std::jthread reader;
|
||||
{
|
||||
auto guard = device.lock_unique<&Runtime_Device::value>();
|
||||
reader = std::jthread([&] {
|
||||
Runtime_Read_Capture capture;
|
||||
REQUIRE(erased.runtime_read("serial", &capture, &capture_int) == Runtime_Access_Result::ok);
|
||||
REQUIRE(capture.value == 7);
|
||||
done.release();
|
||||
});
|
||||
REQUIRE(done.try_acquire_for(std::chrono::milliseconds(200)));
|
||||
}
|
||||
reader.join();
|
||||
}
|
||||
int main() {
|
||||
test_runtime_metadata_and_results();
|
||||
test_runtime_access_uses_managed_synchronization();
|
||||
test_runtime_read_only_fast_path();
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
#include <structive/property/property.hpp>
|
||||
#include <barrier>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <semaphore>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
using namespace structive;
|
||||
#define REQUIRE(expression) do { if (!(expression)) { std::fprintf(stderr, "REQUIRE failed: %s:%d: %s\n", __FILE__, __LINE__, #expression); std::abort(); } } while (false)
|
||||
struct Sync_Device : Property_Object<Sync_Device> {
|
||||
Sync_Device() = default;
|
||||
explicit Sync_Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {}
|
||||
int id{1};
|
||||
int a{10};
|
||||
int b{20};
|
||||
int c{30};
|
||||
};
|
||||
template <>
|
||||
struct structive::Type_Descriptor<Sync_Device> {
|
||||
static auto get() {
|
||||
return object<Sync_Device>(
|
||||
synchronization(sync_all_independent),
|
||||
field<&Sync_Device::id>(key<"id">, read_only),
|
||||
field<&Sync_Device::a>(key<"a">),
|
||||
field<&Sync_Device::b>(key<"b">),
|
||||
field<&Sync_Device::c>(key<"c">)
|
||||
);
|
||||
}
|
||||
};
|
||||
static Sync_Device with_plan(Synchronization_Plan plan) {
|
||||
return Sync_Device{property_synchronization(std::move(plan))};
|
||||
}
|
||||
static void test_topology_resolution() {
|
||||
Sync_Device independent;
|
||||
auto ro = independent.lock_slot<&Sync_Device::id>();
|
||||
auto a = independent.lock_slot<&Sync_Device::a>();
|
||||
auto b = independent.lock_slot<&Sync_Device::b>();
|
||||
auto c = independent.lock_slot<&Sync_Device::c>();
|
||||
REQUIRE(ro == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
REQUIRE(independent.resolved_synchronization().lock_count == 3);
|
||||
REQUIRE(a != b && a != c && b != c);
|
||||
auto shared = with_plan(synchronization(sync_all_shared));
|
||||
REQUIRE(shared.resolved_synchronization().lock_count == 1);
|
||||
REQUIRE(shared.lock_slot<&Sync_Device::a>() == shared.lock_slot<&Sync_Device::b>());
|
||||
REQUIRE(shared.lock_slot<&Sync_Device::b>() == shared.lock_slot<&Sync_Device::c>());
|
||||
REQUIRE(shared.lock_slot<&Sync_Device::id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
auto unsynchronized = with_plan(synchronization(sync_all_unsynchronized));
|
||||
REQUIRE(unsynchronized.resolved_synchronization().lock_count == 0);
|
||||
REQUIRE(unsynchronized.lock_slot<&Sync_Device::a>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
REQUIRE(unsynchronized.lock_slot<&Sync_Device::b>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
REQUIRE(unsynchronized.lock_slot<&Sync_Device::c>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
auto grouped = Sync_Device{property_synchronization<Sync_Device>(synchronization(sync_all_independent, sync_group<&Sync_Device::a, &Sync_Device::b>("ab")))};
|
||||
REQUIRE(grouped.resolved_synchronization().lock_count == 2);
|
||||
REQUIRE(grouped.lock_slot<&Sync_Device::a>() == grouped.lock_slot<&Sync_Device::b>());
|
||||
REQUIRE(grouped.lock_slot<&Sync_Device::a>() != grouped.lock_slot<&Sync_Device::c>());
|
||||
auto one_unsynchronized = Sync_Device{property_synchronization<Sync_Device>(synchronization(sync_all_independent, sync_unsynchronized<&Sync_Device::b>()))};
|
||||
REQUIRE(one_unsynchronized.resolved_synchronization().lock_count == 2);
|
||||
REQUIRE(one_unsynchronized.lock_slot<&Sync_Device::b>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
auto one_independent = Sync_Device{property_synchronization<Sync_Device>(synchronization(sync_all_shared, sync_independent<&Sync_Device::c>()))};
|
||||
REQUIRE(one_independent.resolved_synchronization().lock_count == 2);
|
||||
REQUIRE(one_independent.lock_slot<&Sync_Device::a>() == one_independent.lock_slot<&Sync_Device::b>());
|
||||
REQUIRE(one_independent.lock_slot<&Sync_Device::c>() != one_independent.lock_slot<&Sync_Device::a>());
|
||||
auto read_only_grouped = Sync_Device{property_synchronization<Sync_Device>(synchronization(sync_all_independent, sync_group<&Sync_Device::id, &Sync_Device::a>("mixed")))};
|
||||
REQUIRE(read_only_grouped.lock_slot<&Sync_Device::id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||
REQUIRE(read_only_grouped.resolved_synchronization().lock_count == 3);
|
||||
}
|
||||
static void test_plan_validation() {
|
||||
bool unknown_thrown = false;
|
||||
try {
|
||||
Synchronization_Plan plan;
|
||||
plan.independent("missing");
|
||||
auto device = with_plan(std::move(plan));
|
||||
(void)device;
|
||||
} catch (const std::invalid_argument&) {
|
||||
unknown_thrown = true;
|
||||
}
|
||||
REQUIRE(unknown_thrown);
|
||||
bool duplicate_property_thrown = false;
|
||||
try {
|
||||
Synchronization_Plan plan;
|
||||
plan.independent("a").unsynchronized("a");
|
||||
auto device = with_plan(std::move(plan));
|
||||
(void)device;
|
||||
} catch (const std::invalid_argument&) {
|
||||
duplicate_property_thrown = true;
|
||||
}
|
||||
REQUIRE(duplicate_property_thrown);
|
||||
bool empty_group_thrown = false;
|
||||
try {
|
||||
Synchronization_Plan plan;
|
||||
plan.group("empty", std::span<const std::string_view>{});
|
||||
auto device = with_plan(std::move(plan));
|
||||
(void)device;
|
||||
} catch (const std::invalid_argument&) {
|
||||
empty_group_thrown = true;
|
||||
}
|
||||
REQUIRE(empty_group_thrown);
|
||||
}
|
||||
static void test_dynamic_guard_validation() {
|
||||
Sync_Device device;
|
||||
bool unknown_thrown = false;
|
||||
try {
|
||||
auto guard = device.lock_shared({"missing"});
|
||||
(void)guard;
|
||||
} catch (const std::invalid_argument&) {
|
||||
unknown_thrown = true;
|
||||
}
|
||||
REQUIRE(unknown_thrown);
|
||||
bool read_only_unique_thrown = false;
|
||||
try {
|
||||
auto guard = device.lock_unique({"id"});
|
||||
(void)guard;
|
||||
} catch (const std::invalid_argument&) {
|
||||
read_only_unique_thrown = true;
|
||||
}
|
||||
REQUIRE(read_only_unique_thrown);
|
||||
auto read_guard = device.lock_shared({"id"});
|
||||
REQUIRE(read_guard.get<"id">() == 1);
|
||||
}
|
||||
static void test_independent_and_shared_blocking() {
|
||||
Sync_Device independent;
|
||||
std::binary_semaphore independent_other_done{0};
|
||||
{
|
||||
auto guard = independent.lock_unique<&Sync_Device::a>();
|
||||
std::jthread writer([&] {
|
||||
independent.write<&Sync_Device::b>(21);
|
||||
independent_other_done.release();
|
||||
});
|
||||
REQUIRE(independent_other_done.try_acquire_for(std::chrono::milliseconds(200)));
|
||||
}
|
||||
REQUIRE(independent.read<&Sync_Device::b>() == 21);
|
||||
std::binary_semaphore same_done{0};
|
||||
std::jthread same_writer;
|
||||
{
|
||||
auto guard = independent.lock_unique<&Sync_Device::a>();
|
||||
same_writer = std::jthread([&] {
|
||||
independent.write<&Sync_Device::a>(11);
|
||||
same_done.release();
|
||||
});
|
||||
REQUIRE(!same_done.try_acquire_for(std::chrono::milliseconds(20)));
|
||||
}
|
||||
REQUIRE(same_done.try_acquire_for(std::chrono::seconds(2)));
|
||||
same_writer.join();
|
||||
auto shared = with_plan(synchronization(sync_all_shared));
|
||||
std::binary_semaphore shared_done{0};
|
||||
std::jthread shared_writer;
|
||||
{
|
||||
auto guard = shared.lock_unique<&Sync_Device::a>();
|
||||
shared_writer = std::jthread([&] {
|
||||
shared.write<&Sync_Device::b>(22);
|
||||
shared_done.release();
|
||||
});
|
||||
REQUIRE(!shared_done.try_acquire_for(std::chrono::milliseconds(20)));
|
||||
}
|
||||
REQUIRE(shared_done.try_acquire_for(std::chrono::seconds(2)));
|
||||
shared_writer.join();
|
||||
}
|
||||
static void test_static_guard_order_is_stable() {
|
||||
Sync_Device device;
|
||||
std::barrier start{2};
|
||||
std::jthread first([&] {
|
||||
start.arrive_and_wait();
|
||||
for (int index = 0; index < 100; ++index) {
|
||||
auto guard = device.lock_unique<&Sync_Device::a, &Sync_Device::b>();
|
||||
auto a = guard.get<"a">();
|
||||
auto b = guard.get<&Sync_Device::b>();
|
||||
guard.set<"a">(a + 1);
|
||||
guard.set<&Sync_Device::b>(b + 1);
|
||||
}
|
||||
});
|
||||
std::jthread second([&] {
|
||||
start.arrive_and_wait();
|
||||
for (int index = 0; index < 100; ++index) {
|
||||
auto guard = device.lock_unique<&Sync_Device::b, &Sync_Device::a>();
|
||||
auto b = guard.get<"b">();
|
||||
auto a = guard.get<&Sync_Device::a>();
|
||||
guard.set<"b">(b + 1);
|
||||
guard.set<&Sync_Device::a>(a + 1);
|
||||
}
|
||||
});
|
||||
first.join();
|
||||
second.join();
|
||||
REQUIRE(device.read<&Sync_Device::a>() == 210);
|
||||
REQUIRE(device.read<&Sync_Device::b>() == 220);
|
||||
}
|
||||
int main() {
|
||||
test_topology_resolution();
|
||||
test_plan_validation();
|
||||
test_dynamic_guard_validation();
|
||||
test_independent_and_shared_blocking();
|
||||
test_static_guard_order_is_stable();
|
||||
}
|
||||
Reference in New Issue
Block a user