补全边角
This commit is contained in:
+27
-13
@@ -1,16 +1,30 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(Structive LANGUAGES CXX)
|
project(Structive VERSION 1.0.0 LANGUAGES CXX)
|
||||||
if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
|
include(GNUInstallDirs)
|
||||||
|
if(PROJECT_IS_TOP_LEVEL)
|
||||||
|
include(CTest)
|
||||||
|
endif()
|
||||||
|
option(STRUCTIVE_BUILD_EXAMPLES "Build Structive examples" ${PROJECT_IS_TOP_LEVEL})
|
||||||
|
option(STRUCTIVE_BUILD_TESTS "Build Structive tests" ${BUILD_TESTING})
|
||||||
|
option(STRUCTIVE_INSTALL "Install Structive as a standalone CMake package" ${PROJECT_IS_TOP_LEVEL})
|
||||||
|
if(PROJECT_IS_TOP_LEVEL)
|
||||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||||
set(CMAKE_FOLDER "Structive")
|
set(CMAKE_FOLDER "Structive")
|
||||||
endif ()
|
endif()
|
||||||
include(CTest)
|
include("${CMAKE_CURRENT_LIST_DIR}/core/main.cmake")
|
||||||
get_property(_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
|
include("${CMAKE_CURRENT_LIST_DIR}/extensions/main.cmake")
|
||||||
foreach (_target IN LISTS _targets)
|
if(STRUCTIVE_INSTALL)
|
||||||
get_target_property(_folder "${_target}" FOLDER)
|
include(CMakePackageConfigHelpers)
|
||||||
if (_folder STREQUAL "CTestDashboardTargets")
|
install(TARGETS structive_property_core EXPORT StructiveTargets INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
set_property(TARGET "${_target}" PROPERTY FOLDER "Structive/CTest")
|
install(TARGETS structive_property_extensions EXPORT StructiveTargets ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
endif ()
|
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/core/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
endforeach ()
|
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/extensions/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/core/main.cmake)
|
install(EXPORT StructiveTargets FILE StructiveTargets.cmake NAMESPACE structive:: DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Structive")
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/extensions/main.cmake)
|
configure_package_config_file("${CMAKE_CURRENT_LIST_DIR}/cmake/StructiveConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/StructiveConfig.cmake" INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Structive")
|
||||||
|
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/StructiveConfigVersion.cmake" VERSION "${PROJECT_VERSION}" COMPATIBILITY SameMajorVersion)
|
||||||
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/StructiveConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/StructiveConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Structive")
|
||||||
|
endif()
|
||||||
|
if(PROJECT_IS_TOP_LEVEL AND STRUCTIVE_BUILD_TESTS AND STRUCTIVE_INSTALL)
|
||||||
|
add_test(NAME structive_install_consumer_test COMMAND "${CMAKE_COMMAND}" "-DSTRUCTIVE_SOURCE_DIR=${CMAKE_CURRENT_LIST_DIR}" "-DSTRUCTIVE_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}" "-DSTRUCTIVE_CONFIG=$<CONFIG>" "-DSTRUCTIVE_GENERATOR=${CMAKE_GENERATOR}" "-DSTRUCTIVE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}" "-DSTRUCTIVE_GENERATOR_TOOLSET=${CMAKE_GENERATOR_TOOLSET}" -P "${CMAKE_CURRENT_LIST_DIR}/cmake/RunInstallConsumer.cmake")
|
||||||
|
set_tests_properties(structive_install_consumer_test PROPERTIES LABELS "install;package")
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ runtime_read(key, context, callback)
|
|||||||
runtime_write(key, type_info, value)
|
runtime_write(key, type_info, value)
|
||||||
```
|
```
|
||||||
|
|
||||||
They return `ok`, `unknown_property`, `not_readable`, `not_writable` or `type_mismatch`. Runtime write requires an exact type match and performs no implicit conversion. The read callback receives a borrowed pointer that is valid only during the callback; synchronized writable state remains read-locked while the callback executes. Stored read-only properties retain the same zero-lock fast path as typed reads.
|
They return `ok`, `unknown_property`, `not_readable`, `not_writable`, `unsupported_runtime_write` or `type_mismatch`. Runtime write is an exact-type copy-input boundary and performs no implicit conversion. A property can remain intrinsically `writable` while exposing `runtime_copy_writable == false` when its accessor requires move-only input; typed `write` still supports that property. The read callback receives a borrowed pointer that is valid only during the callback; synchronized writable state remains read-locked while the callback executes. Stored read-only properties retain the same zero-lock fast path as typed reads.
|
||||||
|
|
||||||
Core intentionally does not impose `variant`, `any`, conversion registries or serialization ownership on this boundary. Higher-level adapters may wrap it. There is no runtime access mode or access-control policy: an external system decides what it exposes, while Structive reports only intrinsic property capability. See [Core Guide: Runtime access](docs/CORE_GUIDE.md#22-runtime-type-erased-access) and `core/tests/runtime_api_test.cpp`.
|
Core intentionally does not impose `variant`, `any`, conversion registries or serialization ownership on this boundary. Higher-level adapters may wrap it. There is no runtime access mode or access-control policy: an external system decides what it exposes, while Structive reports only intrinsic property capability. See [Core Guide: Runtime access](docs/CORE_GUIDE.md#22-runtime-type-erased-access) and `core/tests/runtime_api_test.cpp`.
|
||||||
|
|
||||||
@@ -354,6 +354,8 @@ cmake --build build
|
|||||||
ctest --test-dir build --output-on-failure
|
ctest --test-dir build --output-on-failure
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Standalone configuration exposes `STRUCTIVE_BUILD_EXAMPLES`, `STRUCTIVE_BUILD_TESTS` and `STRUCTIVE_INSTALL`. Examples default on only when Structive is the top-level project; tests follow `BUILD_TESTING`; standalone install defaults on. Installation exports `structive::property_core` and `structive::property_extensions` through `find_package(Structive CONFIG)`, and the standalone CTest suite verifies an external install consumer.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Design Philosophy and Principles](docs/DESIGN.md)
|
- [Design Philosophy and Principles](docs/DESIGN.md)
|
||||||
|
|||||||
+3
-1
@@ -306,7 +306,7 @@ runtime_read(key, context, callback)
|
|||||||
runtime_write(key, type_info, value)
|
runtime_write(key, type_info, value)
|
||||||
```
|
```
|
||||||
|
|
||||||
返回 `ok`、`unknown_property`、`not_readable`、`not_writable` 或 `type_mismatch`。Runtime write 要求类型完全一致,不做隐式转换。Read callback 收到的是借用指针,只在 callback 期间有效;对于需要同步的 writable state,callback 执行期间 managed read lock 仍然持有。Stored read-only Property 继续走和 typed read 一样的 zero-lock fast path。
|
返回 `ok`、`unknown_property`、`not_readable`、`not_writable`、`unsupported_runtime_write` 或 `type_mismatch`。Runtime write 是精确类型的 copy-input 边界,不做隐式转换。如果 Accessor 只能接收 move-only 输入,Property 仍然可以保持 intrinsic `writable`,但会暴露 `runtime_copy_writable == false`;typed `write` 仍然支持这种 Property。Read callback 收到的是借用指针,只在 callback 期间有效;对于需要同步的 writable state,callback 执行期间 managed read lock 仍然持有。Stored read-only Property 继续走和 typed read 一样的 zero-lock fast path。
|
||||||
|
|
||||||
Core 不在这个边界强制引入 `variant`、`any`、转换注册表或 serialization 所有权策略,上层 Adapter 可以按领域需要封装。这里没有 runtime access mode,也没有访问控制;外部系统自行决定暴露策略,Structive 只报告 Property intrinsic capability。详细契约见 [Core Guide: Runtime Access](docs/CORE_GUIDE.zh-CN.md#22-runtime-type-erased-access),测试见 `core/tests/runtime_api_test.cpp`。
|
Core 不在这个边界强制引入 `variant`、`any`、转换注册表或 serialization 所有权策略,上层 Adapter 可以按领域需要封装。这里没有 runtime access mode,也没有访问控制;外部系统自行决定暴露策略,Structive 只报告 Property intrinsic capability。详细契约见 [Core Guide: Runtime Access](docs/CORE_GUIDE.zh-CN.md#22-runtime-type-erased-access),测试见 `core/tests/runtime_api_test.cpp`。
|
||||||
|
|
||||||
@@ -360,6 +360,8 @@ cmake --build build
|
|||||||
ctest --test-dir build --output-on-failure
|
ctest --test-dir build --output-on-failure
|
||||||
```
|
```
|
||||||
|
|
||||||
|
独立构建提供 `STRUCTIVE_BUILD_EXAMPLES`、`STRUCTIVE_BUILD_TESTS`、`STRUCTIVE_INSTALL`。Example 只在 Structive 作为顶层工程时默认开启;测试跟随 `BUILD_TESTING`;独立安装默认开启。安装后可通过 `find_package(Structive CONFIG)` 使用 `structive::property_core` 与 `structive::property_extensions`,并且 standalone CTest 会真实验证外部 install consumer。
|
||||||
|
|
||||||
## 详细文档
|
## 详细文档
|
||||||
|
|
||||||
- [设计理念与原则](docs/DESIGN.zh-CN.md)
|
- [设计理念与原则](docs/DESIGN.zh-CN.md)
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
if(NOT DEFINED STRUCTIVE_SOURCE_DIR OR NOT DEFINED STRUCTIVE_BINARY_DIR)
|
||||||
|
message(FATAL_ERROR "STRUCTIVE_SOURCE_DIR and STRUCTIVE_BINARY_DIR are required")
|
||||||
|
endif()
|
||||||
|
set(prefix "${STRUCTIVE_BINARY_DIR}/install_consumer_prefix")
|
||||||
|
set(consumer_binary "${STRUCTIVE_BINARY_DIR}/install_consumer_build")
|
||||||
|
file(REMOVE_RECURSE "${prefix}" "${consumer_binary}")
|
||||||
|
set(build_command "${CMAKE_COMMAND}" --build "${STRUCTIVE_BINARY_DIR}" --target structive_property_extensions)
|
||||||
|
if(DEFINED STRUCTIVE_CONFIG AND NOT STRUCTIVE_CONFIG STREQUAL "")
|
||||||
|
list(APPEND build_command --config "${STRUCTIVE_CONFIG}")
|
||||||
|
endif()
|
||||||
|
execute_process(COMMAND ${build_command} RESULT_VARIABLE build_result)
|
||||||
|
if(NOT build_result EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to build Structive before install consumer test")
|
||||||
|
endif()
|
||||||
|
set(install_command "${CMAKE_COMMAND}" --install "${STRUCTIVE_BINARY_DIR}" --prefix "${prefix}")
|
||||||
|
if(DEFINED STRUCTIVE_CONFIG AND NOT STRUCTIVE_CONFIG STREQUAL "")
|
||||||
|
list(APPEND install_command --config "${STRUCTIVE_CONFIG}")
|
||||||
|
endif()
|
||||||
|
execute_process(COMMAND ${install_command} RESULT_VARIABLE install_result)
|
||||||
|
if(NOT install_result EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to install Structive")
|
||||||
|
endif()
|
||||||
|
set(configure_command "${CMAKE_COMMAND}" -S "${STRUCTIVE_SOURCE_DIR}/install_consumer" -B "${consumer_binary}" "-DCMAKE_PREFIX_PATH=${prefix}")
|
||||||
|
if(DEFINED STRUCTIVE_GENERATOR AND NOT STRUCTIVE_GENERATOR STREQUAL "")
|
||||||
|
list(APPEND configure_command -G "${STRUCTIVE_GENERATOR}")
|
||||||
|
endif()
|
||||||
|
if(DEFINED STRUCTIVE_GENERATOR_PLATFORM AND NOT STRUCTIVE_GENERATOR_PLATFORM STREQUAL "")
|
||||||
|
list(APPEND configure_command -A "${STRUCTIVE_GENERATOR_PLATFORM}")
|
||||||
|
endif()
|
||||||
|
if(DEFINED STRUCTIVE_GENERATOR_TOOLSET AND NOT STRUCTIVE_GENERATOR_TOOLSET STREQUAL "")
|
||||||
|
list(APPEND configure_command -T "${STRUCTIVE_GENERATOR_TOOLSET}")
|
||||||
|
endif()
|
||||||
|
execute_process(COMMAND ${configure_command} RESULT_VARIABLE configure_result)
|
||||||
|
if(NOT configure_result EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to configure Structive install consumer")
|
||||||
|
endif()
|
||||||
|
set(consumer_build_command "${CMAKE_COMMAND}" --build "${consumer_binary}")
|
||||||
|
if(DEFINED STRUCTIVE_CONFIG AND NOT STRUCTIVE_CONFIG STREQUAL "")
|
||||||
|
list(APPEND consumer_build_command --config "${STRUCTIVE_CONFIG}")
|
||||||
|
endif()
|
||||||
|
execute_process(COMMAND ${consumer_build_command} RESULT_VARIABLE consumer_build_result)
|
||||||
|
if(NOT consumer_build_result EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to build Structive install consumer")
|
||||||
|
endif()
|
||||||
|
set(ctest_command "${CMAKE_CTEST_COMMAND}" --test-dir "${consumer_binary}" --output-on-failure)
|
||||||
|
if(DEFINED STRUCTIVE_CONFIG AND NOT STRUCTIVE_CONFIG STREQUAL "")
|
||||||
|
list(APPEND ctest_command -C "${STRUCTIVE_CONFIG}")
|
||||||
|
endif()
|
||||||
|
execute_process(COMMAND ${ctest_command} RESULT_VARIABLE test_result)
|
||||||
|
if(NOT test_result EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Structive install consumer failed")
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/StructiveTargets.cmake")
|
||||||
|
set(Structive_Core_FOUND TRUE)
|
||||||
|
set(Structive_Extensions_FOUND TRUE)
|
||||||
|
check_required_components(Structive)
|
||||||
@@ -43,6 +43,7 @@ struct Property_Descriptor {
|
|||||||
static constexpr bool writable = Accessor::writable && (intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write);
|
static constexpr bool writable = Accessor::writable && (intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write);
|
||||||
static constexpr bool synchronized_view_read = Accessor::synchronized_view_read;
|
static constexpr bool synchronized_view_read = Accessor::synchronized_view_read;
|
||||||
static constexpr bool trusted_object_access = Accessor::trusted_object_access;
|
static constexpr bool trusted_object_access = Accessor::trusted_object_access;
|
||||||
|
static constexpr bool runtime_copy_writable = writable && requires(const Accessor& accessor, object_type& object, const value_type& candidate) { accessor.write(object, candidate); };
|
||||||
static_assert(unique_single_value_categories<Attributes...>());
|
static_assert(unique_single_value_categories<Attributes...>());
|
||||||
static_assert(!((intrinsic_capability == Property_Capability::read || intrinsic_capability == Property_Capability::read_write) && !Accessor::readable), "property capability requests read from a non-readable accessor");
|
static_assert(!((intrinsic_capability == Property_Capability::read || intrinsic_capability == Property_Capability::read_write) && !Accessor::readable), "property capability requests read from a non-readable accessor");
|
||||||
static_assert(!((intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write) && !Accessor::writable), "property capability requests write from a non-writable accessor");
|
static_assert(!((intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write) && !Accessor::writable), "property capability requests write from a non-writable accessor");
|
||||||
@@ -55,9 +56,11 @@ struct Property_Descriptor {
|
|||||||
template <class Category>
|
template <class Category>
|
||||||
using attribute_type = find_attribute_in_list_t<Category, attribute_types>;
|
using attribute_type = find_attribute_in_list_t<Category, attribute_types>;
|
||||||
template <class Category>
|
template <class Category>
|
||||||
static constexpr bool has_attribute = !std::same_as<attribute_type<Category>, void>;
|
static constexpr std::size_t attribute_count = count_attribute_category<Category, Attributes...>();
|
||||||
template <class Category>
|
template <class Category>
|
||||||
constexpr decltype(auto) attribute() const requires has_attribute<Category> {
|
static constexpr bool has_attribute = attribute_count<Category> != 0;
|
||||||
|
template <class Category>
|
||||||
|
constexpr decltype(auto) attribute() const requires (attribute_count<Category> == 1) {
|
||||||
using target = attribute_type<Category>;
|
using target = attribute_type<Category>;
|
||||||
return std::get<target>(attributes);
|
return std::get<target>(attributes);
|
||||||
}
|
}
|
||||||
@@ -70,6 +73,17 @@ struct Property_Descriptor {
|
|||||||
(function(values), ...);
|
(function(values), ...);
|
||||||
}, attributes);
|
}, attributes);
|
||||||
}
|
}
|
||||||
|
template <class Category, class Function>
|
||||||
|
constexpr void for_each_attribute(Function&& function) const {
|
||||||
|
std::apply([&](const auto&... values) {
|
||||||
|
([&] {
|
||||||
|
using type = std::remove_cvref_t<decltype(values)>;
|
||||||
|
if constexpr (std::same_as<attribute_category_of_t<type>, Category>) {
|
||||||
|
function(values);
|
||||||
|
}
|
||||||
|
}(), ...);
|
||||||
|
}, attributes);
|
||||||
|
}
|
||||||
template <class Function>
|
template <class Function>
|
||||||
constexpr void for_each_constraint(Function&& function) const {
|
constexpr void for_each_constraint(Function&& function) const {
|
||||||
std::apply([&](const auto&... values) {
|
std::apply([&](const auto&... values) {
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ enum class Runtime_Access_Result {
|
|||||||
unknown_property,
|
unknown_property,
|
||||||
not_readable,
|
not_readable,
|
||||||
not_writable,
|
not_writable,
|
||||||
|
unsupported_runtime_write,
|
||||||
type_mismatch
|
type_mismatch
|
||||||
};
|
};
|
||||||
/// Callback used by `runtime_read`.
|
/// Callback used by `runtime_read`.
|
||||||
@@ -123,8 +124,8 @@ public:
|
|||||||
Runtime_Access_Result runtime_read(std::string_view key, void* context, Runtime_Read_Callback callback) const {
|
Runtime_Access_Result runtime_read(std::string_view key, void* context, Runtime_Read_Callback callback) const {
|
||||||
return runtime_interface_->read(*this, key, context, callback);
|
return runtime_interface_->read(*this, key, context, callback);
|
||||||
}
|
}
|
||||||
/// Looks up `key` at runtime and performs the same managed write used by typed `write`.
|
/// Looks up `key` at runtime and performs a copy-input managed write.
|
||||||
/// `value_type` must exactly match the property's declared value type and `value` must point to a live object of that exact type for the duration of the call. No numeric, string or user-defined conversion is attempted.
|
/// `value_type` must exactly match the property's declared value type and `value` must point to a live object of that exact type for the duration of the call. No numeric, string or user-defined conversion is attempted. Intrinsically writable properties that require move-only input report `unsupported_runtime_write`; typed `write` remains available for them.
|
||||||
Runtime_Access_Result runtime_write(std::string_view key, const std::type_info& value_type, const void* value) {
|
Runtime_Access_Result runtime_write(std::string_view key, const std::type_info& value_type, const void* value) {
|
||||||
return runtime_interface_->write(*this, key, value_type, value);
|
return runtime_interface_->write(*this, key, value_type, value);
|
||||||
}
|
}
|
||||||
@@ -656,7 +657,7 @@ private:
|
|||||||
return Runtime_Access_Result::not_readable;
|
return Runtime_Access_Result::not_readable;
|
||||||
}
|
}
|
||||||
Runtime_Access_Result result = Runtime_Access_Result::unknown_property;
|
Runtime_Access_Result result = Runtime_Access_Result::unknown_property;
|
||||||
visit_schema_property(type_descriptor<Derived>(), key, [&](auto property_index_constant, const auto&) {
|
visit_schema_property_at(type_descriptor<Derived>(), *index, [&](auto property_index_constant, const auto&) {
|
||||||
constexpr std::size_t property_index = decltype(property_index_constant)::value;
|
constexpr std::size_t property_index = decltype(property_index_constant)::value;
|
||||||
using Property = typename Schema::template property_type<property_index>;
|
using Property = typename Schema::template property_type<property_index>;
|
||||||
using Value = typename Property::value_type;
|
using Value = typename Property::value_type;
|
||||||
@@ -707,13 +708,12 @@ private:
|
|||||||
return Runtime_Access_Result::not_writable;
|
return Runtime_Access_Result::not_writable;
|
||||||
}
|
}
|
||||||
Runtime_Access_Result result = Runtime_Access_Result::unknown_property;
|
Runtime_Access_Result result = Runtime_Access_Result::unknown_property;
|
||||||
visit_schema_property(type_descriptor<Derived>(), key, [&](auto property_index_constant, const auto&) {
|
visit_schema_property_at(type_descriptor<Derived>(), *index, [&](auto property_index_constant, const auto&) {
|
||||||
constexpr std::size_t property_index = decltype(property_index_constant)::value;
|
constexpr std::size_t property_index = decltype(property_index_constant)::value;
|
||||||
using Property = typename Schema::template property_type<property_index>;
|
using Property = typename Schema::template property_type<property_index>;
|
||||||
using Accessor = typename Property::accessor_type;
|
|
||||||
using Value = typename Property::value_type;
|
using Value = typename Property::value_type;
|
||||||
if constexpr (!Schema::template property_type<property_index>::writable || !requires(const Accessor& accessor, Derived& object, const Value& candidate) { accessor.write(object, candidate); }) {
|
if constexpr (!Property::runtime_copy_writable) {
|
||||||
result = Runtime_Access_Result::not_writable;
|
result = Runtime_Access_Result::unsupported_runtime_write;
|
||||||
} else if (value_type != typeid(Value)) {
|
} else if (value_type != typeid(Value)) {
|
||||||
result = Runtime_Access_Result::type_mismatch;
|
result = Runtime_Access_Result::type_mismatch;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ consteval std::string_view declared_property_key() {
|
|||||||
}
|
}
|
||||||
template <class... Properties>
|
template <class... Properties>
|
||||||
consteval bool unique_property_keys() {
|
consteval bool unique_property_keys() {
|
||||||
constexpr std::array keys{declared_property_key<Properties>()...};
|
constexpr std::array<std::string_view, sizeof...(Properties)> keys{declared_property_key<Properties>()...};
|
||||||
for (std::size_t i = 0; i < keys.size(); ++i) {
|
for (std::size_t i = 0; i < keys.size(); ++i) {
|
||||||
for (std::size_t j = i + 1; j < keys.size(); ++j) {
|
for (std::size_t j = i + 1; j < keys.size(); ++j) {
|
||||||
if (keys[i] == keys[j]) {
|
if (keys[i] == keys[j]) {
|
||||||
@@ -374,19 +374,24 @@ Synchronization_Plan materialize_synchronization_plan(Source&& source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <Valid_Property_Schema Schema, class Function>
|
template <Valid_Property_Schema Schema, class Function>
|
||||||
bool visit_schema_property(const Schema& schema, std::string_view key_value, Function&& function) {
|
bool visit_schema_property_at(const Schema& schema, std::size_t index_value, Function&& function) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
schema.for_each_property([&](auto index, const auto& descriptor) {
|
schema.for_each_property([&](auto index, const auto& descriptor) {
|
||||||
if (!found) {
|
if (!found && decltype(index)::value == index_value) {
|
||||||
using property_type = std::remove_cvref_t<decltype(descriptor)>;
|
std::invoke(function, index, descriptor);
|
||||||
if (declared_property_key<property_type>() == key_value) {
|
found = true;
|
||||||
std::invoke(function, index, descriptor);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
template <Valid_Property_Schema Schema, class Function>
|
||||||
|
bool visit_schema_property(const Schema& schema, std::string_view key_value, Function&& function) {
|
||||||
|
auto index = schema_property_index<Schema>(key_value);
|
||||||
|
if (!index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return visit_schema_property_at(schema, *index, std::forward<Function>(function));
|
||||||
|
}
|
||||||
template <class Object, class Defaults, class Source, class... Properties>
|
template <class Object, class Defaults, class Source, class... Properties>
|
||||||
auto make_object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type<std::remove_cvref_t<Source>> {
|
auto make_object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type<std::remove_cvref_t<Source>> {
|
||||||
using schema_type = Object_Schema<Object, std::decay_t<Defaults>, std::decay_t<Properties>...>;
|
using schema_type = Object_Schema<Object, std::decay_t<Defaults>, std::decay_t<Properties>...>;
|
||||||
|
|||||||
+61
-10
@@ -1,13 +1,16 @@
|
|||||||
add_library(structive_property_core INTERFACE)
|
add_library(structive_property_core INTERFACE)
|
||||||
add_library(structive::property_core ALIAS structive_property_core)
|
add_library(structive::property_core ALIAS structive_property_core)
|
||||||
target_include_directories(structive_property_core INTERFACE "${CMAKE_CURRENT_LIST_DIR}/include")
|
set_target_properties(structive_property_core PROPERTIES EXPORT_NAME property_core)
|
||||||
|
target_include_directories(structive_property_core INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
|
||||||
target_compile_features(structive_property_core INTERFACE cxx_std_20)
|
target_compile_features(structive_property_core INTERFACE cxx_std_20)
|
||||||
add_executable(structive_property_core_example "${CMAKE_CURRENT_LIST_DIR}/example/main.cpp")
|
if(STRUCTIVE_BUILD_EXAMPLES)
|
||||||
target_link_libraries(structive_property_core_example PRIVATE structive::property_core)
|
add_executable(structive_property_core_example "${CMAKE_CURRENT_LIST_DIR}/example/main.cpp")
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
target_link_libraries(structive_property_core_example PRIVATE structive::property_core)
|
||||||
target_compile_options(structive_property_core_example PRIVATE -Wall -Wextra -Wpedantic)
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
endif ()
|
target_compile_options(structive_property_core_example PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
if (BUILD_TESTING)
|
endif()
|
||||||
|
endif()
|
||||||
|
if(STRUCTIVE_BUILD_TESTS)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
add_executable(structive_property_core_test "${CMAKE_CURRENT_LIST_DIR}/tests/property_core_test.cpp")
|
add_executable(structive_property_core_test "${CMAKE_CURRENT_LIST_DIR}/tests/property_core_test.cpp")
|
||||||
add_executable(structive_property_runtime_api_test "${CMAKE_CURRENT_LIST_DIR}/tests/runtime_api_test.cpp")
|
add_executable(structive_property_runtime_api_test "${CMAKE_CURRENT_LIST_DIR}/tests/runtime_api_test.cpp")
|
||||||
@@ -15,12 +18,60 @@ if (BUILD_TESTING)
|
|||||||
target_link_libraries(structive_property_core_test PRIVATE structive::property_core Threads::Threads)
|
target_link_libraries(structive_property_core_test PRIVATE structive::property_core Threads::Threads)
|
||||||
target_link_libraries(structive_property_runtime_api_test PRIVATE structive::property_core Threads::Threads)
|
target_link_libraries(structive_property_runtime_api_test PRIVATE structive::property_core Threads::Threads)
|
||||||
target_link_libraries(structive_property_synchronization_test PRIVATE structive::property_core Threads::Threads)
|
target_link_libraries(structive_property_synchronization_test PRIVATE structive::property_core Threads::Threads)
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
target_compile_options(structive_property_core_test PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(structive_property_core_test PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
target_compile_options(structive_property_runtime_api_test PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(structive_property_runtime_api_test PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
target_compile_options(structive_property_synchronization_test PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(structive_property_synchronization_test PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
endif ()
|
endif()
|
||||||
add_test(NAME structive_property_core_test COMMAND structive_property_core_test)
|
add_test(NAME structive_property_core_test COMMAND structive_property_core_test)
|
||||||
add_test(NAME structive_property_runtime_api_test COMMAND structive_property_runtime_api_test)
|
add_test(NAME structive_property_runtime_api_test COMMAND structive_property_runtime_api_test)
|
||||||
add_test(NAME structive_property_synchronization_test COMMAND structive_property_synchronization_test)
|
add_test(NAME structive_property_synchronization_test COMMAND structive_property_synchronization_test)
|
||||||
endif ()
|
set_tests_properties(structive_property_core_test structive_property_runtime_api_test structive_property_synchronization_test PROPERTIES LABELS "unit;core")
|
||||||
|
function(structive_add_core_header_test header)
|
||||||
|
string(REPLACE "/" "_" target_suffix "${header}")
|
||||||
|
string(REPLACE "." "_" target_suffix "${target_suffix}")
|
||||||
|
set(target "structive_header_${target_suffix}")
|
||||||
|
set(source "${CMAKE_CURRENT_BINARY_DIR}/structive_header_tests/${target}.cpp")
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/structive_header_tests")
|
||||||
|
file(WRITE "${source}" "#include <${header}>\nint main() {\n return 0;\n}\n")
|
||||||
|
add_executable("${target}" "${source}")
|
||||||
|
target_link_libraries("${target}" PRIVATE structive::property_core)
|
||||||
|
add_test(NAME "${target}" COMMAND "${target}")
|
||||||
|
set_tests_properties("${target}" PROPERTIES LABELS "header;core")
|
||||||
|
endfunction()
|
||||||
|
set(structive_core_headers
|
||||||
|
structive/property/accessor.hpp
|
||||||
|
structive/property/attributes.hpp
|
||||||
|
structive/property/descriptor.hpp
|
||||||
|
structive/property/fixed_string.hpp
|
||||||
|
structive/property/meta.hpp
|
||||||
|
structive/property/property.hpp
|
||||||
|
structive/property/property_object.hpp
|
||||||
|
structive/property/schema.hpp
|
||||||
|
structive/property/synchronization.hpp
|
||||||
|
structive/property/type_descriptor.hpp
|
||||||
|
structive/property/validation.hpp
|
||||||
|
)
|
||||||
|
foreach(header IN LISTS structive_core_headers)
|
||||||
|
structive_add_core_header_test("${header}")
|
||||||
|
endforeach()
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
function(structive_expect_compile_failure name source)
|
||||||
|
file(READ "${source}" source_text)
|
||||||
|
set(CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/include")
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(result_variable "STRUCTIVE_COMPILE_FAIL_${name}")
|
||||||
|
unset("${result_variable}" CACHE)
|
||||||
|
check_cxx_source_compiles("${source_text}" "${result_variable}")
|
||||||
|
if(${result_variable})
|
||||||
|
message(FATAL_ERROR "Structive compile-fail contract unexpectedly compiled: ${name}")
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
structive_expect_compile_failure(duplicate_key "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/duplicate_key.cpp")
|
||||||
|
structive_expect_compile_failure(duplicate_storage "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/duplicate_storage.cpp")
|
||||||
|
structive_expect_compile_failure(missing_key "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/missing_key.cpp")
|
||||||
|
structive_expect_compile_failure(capability_mismatch "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/capability_mismatch.cpp")
|
||||||
|
structive_expect_compile_failure(incompatible_constraint "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/incompatible_constraint.cpp")
|
||||||
|
structive_expect_compile_failure(foreign_sync_member "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/foreign_sync_member.cpp")
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include <structive/property/property.hpp>
|
||||||
|
struct Capability_Mismatch {
|
||||||
|
const int value{};
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
auto schema = structive::object<Capability_Mismatch>(structive::field<&Capability_Mismatch::value>(structive::key<"value">, structive::read_write));
|
||||||
|
return static_cast<int>(schema.property_count);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include <structive/property/property.hpp>
|
||||||
|
struct Duplicate_Key {
|
||||||
|
int left{};
|
||||||
|
int right{};
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
auto schema = structive::object<Duplicate_Key>(structive::field<&Duplicate_Key::left>(structive::key<"value">), structive::field<&Duplicate_Key::right>(structive::key<"value">));
|
||||||
|
return static_cast<int>(schema.property_count);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include <structive/property/property.hpp>
|
||||||
|
struct Duplicate_Storage {
|
||||||
|
int value{};
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
auto schema = structive::object<Duplicate_Storage>(structive::field<&Duplicate_Storage::value>(structive::key<"first">), structive::field<&Duplicate_Storage::value>(structive::key<"second">));
|
||||||
|
return static_cast<int>(schema.property_count);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include <structive/property/property.hpp>
|
||||||
|
struct Foreign {
|
||||||
|
int value{};
|
||||||
|
};
|
||||||
|
struct Local {
|
||||||
|
int value{};
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
auto schema = structive::object<Local>(structive::synchronization(structive::sync_independent<&Foreign::value>()), structive::field<&Local::value>(structive::key<"value">));
|
||||||
|
return static_cast<int>(schema.property_count);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#include <structive/property/property.hpp>
|
||||||
|
#include <string>
|
||||||
|
struct Incompatible_Constraint {
|
||||||
|
int value{};
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
auto only_string = structive::constraint<"string_only">([](const std::string&) { return true; });
|
||||||
|
auto schema = structive::object<Incompatible_Constraint>(structive::field<&Incompatible_Constraint::value>(structive::key<"value">, only_string));
|
||||||
|
return static_cast<int>(schema.property_count);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include <structive/property/property.hpp>
|
||||||
|
struct Missing_Key {
|
||||||
|
int value{};
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
auto schema = structive::object<Missing_Key>(structive::field<&Missing_Key::value>());
|
||||||
|
return static_cast<int>(schema.property_count);
|
||||||
|
}
|
||||||
@@ -20,6 +20,16 @@ struct Test_Tag_Attribute {
|
|||||||
};
|
};
|
||||||
template <int Value>
|
template <int Value>
|
||||||
inline constexpr Test_Tag_Attribute<Value> test_tag{};
|
inline constexpr Test_Tag_Attribute<Value> test_tag{};
|
||||||
|
struct Test_Multi_Category {};
|
||||||
|
template <int Value>
|
||||||
|
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 <int Value>
|
||||||
|
inline constexpr Test_Multi_Attribute<Value> test_multi{};
|
||||||
#define REQUIRE(expression) do { if (!(expression)) { std::fprintf(stderr, "REQUIRE failed: %s:%d: %s\n", __FILE__, __LINE__, #expression); std::abort(); } } while (false)
|
#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> {
|
struct Device : Property_Object<Device> {
|
||||||
Device() = default;
|
Device() = default;
|
||||||
@@ -35,7 +45,7 @@ struct structive::Type_Descriptor<Device> {
|
|||||||
static auto get() {
|
static auto get() {
|
||||||
return object<Device>(
|
return object<Device>(
|
||||||
synchronization(sync_all_independent, sync_group<&Device::min_speed, &Device::max_speed>("speed_range")),
|
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::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::pressure>(key<"pressure">),
|
||||||
field<&Device::min_speed>(key<"minimum_speed">),
|
field<&Device::min_speed>(key<"minimum_speed">),
|
||||||
field<&Device::max_speed>(key<"maximum_speed">),
|
field<&Device::max_speed>(key<"maximum_speed">),
|
||||||
@@ -106,6 +116,13 @@ struct structive::Type_Descriptor<Read_Only_Device> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
struct Empty_Device : Property_Object<Empty_Device> {};
|
||||||
|
template <>
|
||||||
|
struct structive::Type_Descriptor<Empty_Device> {
|
||||||
|
static auto get() {
|
||||||
|
return object<Empty_Device>();
|
||||||
|
}
|
||||||
|
};
|
||||||
struct Pure_Read_Only_Device : Property_Object<Pure_Read_Only_Device> {
|
struct Pure_Read_Only_Device : Property_Object<Pure_Read_Only_Device> {
|
||||||
int id{21};
|
int id{21};
|
||||||
int version{4};
|
int version{4};
|
||||||
@@ -120,6 +137,10 @@ struct structive::Type_Descriptor<Pure_Read_Only_Device> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
template <class Property>
|
||||||
|
concept Can_Read_Multi_Attribute_As_Single = requires(const Property& property_value) {
|
||||||
|
property_value.template attribute<Test_Multi_Category>();
|
||||||
|
};
|
||||||
template <class Object>
|
template <class Object>
|
||||||
concept Can_Write_Immutable = requires(Object& object) {
|
concept Can_Write_Immutable = requires(Object& object) {
|
||||||
object.template write<&Device::immutable_id>(1);
|
object.template write<&Device::immutable_id>(1);
|
||||||
@@ -180,7 +201,24 @@ int main() {
|
|||||||
REQUIRE(schema.template property<&Device::max_speed>().key() == "maximum_speed");
|
REQUIRE(schema.template property<&Device::max_speed>().key() == "maximum_speed");
|
||||||
using Temperature_Property = std::remove_cvref_t<decltype(schema.template property<&Device::temperature>())>;
|
using Temperature_Property = std::remove_cvref_t<decltype(schema.template property<&Device::temperature>())>;
|
||||||
static_assert(Temperature_Property::template has_attribute<Test_Tag_Category>);
|
static_assert(Temperature_Property::template has_attribute<Test_Tag_Category>);
|
||||||
|
static_assert(Temperature_Property::template attribute_count<Test_Tag_Category> == 1);
|
||||||
|
static_assert(Temperature_Property::template attribute_count<Test_Multi_Category> == 2);
|
||||||
|
static_assert(!Can_Read_Multi_Attribute_As_Single<Temperature_Property>);
|
||||||
REQUIRE(Temperature_Property::template attribute_type<Test_Tag_Category>::value == 7);
|
REQUIRE(Temperature_Property::template attribute_type<Test_Tag_Category>::value == 7);
|
||||||
|
int multi_sum = 0;
|
||||||
|
schema.template property<&Device::temperature>().template for_each_attribute<Test_Multi_Category>([&](const auto& attribute) {
|
||||||
|
multi_sum += std::remove_cvref_t<decltype(attribute)>::value;
|
||||||
|
});
|
||||||
|
REQUIRE(multi_sum == 8);
|
||||||
|
const auto& empty_schema = type_descriptor<Empty_Device>();
|
||||||
|
static_assert(type_descriptor_schema_t<Empty_Device>::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;
|
Device device;
|
||||||
REQUIRE(device.resolved_synchronization().lock_count == 3);
|
REQUIRE(device.resolved_synchronization().lock_count == 3);
|
||||||
REQUIRE(device.lock_slot<&Device::immutable_id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
REQUIRE(device.lock_slot<&Device::immutable_id>() == Resolved_Synchronization_View::unsynchronized_slot);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <semaphore>
|
#include <semaphore>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
@@ -24,6 +25,15 @@ struct structive::Type_Descriptor<Runtime_Device> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
struct Move_Only_Runtime_Device : Property_Object<Move_Only_Runtime_Device> {
|
||||||
|
std::unique_ptr<int> value{std::make_unique<int>(1)};
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct structive::Type_Descriptor<Move_Only_Runtime_Device> {
|
||||||
|
static auto get() {
|
||||||
|
return object<Move_Only_Runtime_Device>(field<&Move_Only_Runtime_Device::value>(key<"value">));
|
||||||
|
}
|
||||||
|
};
|
||||||
struct Runtime_Read_Capture {
|
struct Runtime_Read_Capture {
|
||||||
std::size_t calls{};
|
std::size_t calls{};
|
||||||
std::size_t index{};
|
std::size_t index{};
|
||||||
@@ -82,6 +92,20 @@ static void test_runtime_metadata_and_results() {
|
|||||||
REQUIRE(erased.runtime_write("value", typeid(double), &wrong_type) == Runtime_Access_Result::type_mismatch);
|
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);
|
REQUIRE(erased.runtime_write("missing", typeid(int), &value) == Runtime_Access_Result::unknown_property);
|
||||||
}
|
}
|
||||||
|
static void test_runtime_copy_write_boundary() {
|
||||||
|
using Schema = type_descriptor_schema_t<Move_Only_Runtime_Device>;
|
||||||
|
using Property = typename Schema::template property_type<0>;
|
||||||
|
static_assert(Property::writable);
|
||||||
|
static_assert(!Property::runtime_copy_writable);
|
||||||
|
Move_Only_Runtime_Device device;
|
||||||
|
device.write<&Move_Only_Runtime_Device::value>(std::make_unique<int>(9));
|
||||||
|
REQUIRE(*device.value == 9);
|
||||||
|
Property_Object_Base& erased = device;
|
||||||
|
std::unique_ptr<int> replacement = std::make_unique<int>(11);
|
||||||
|
REQUIRE(erased.runtime_write("value", typeid(std::unique_ptr<int>), &replacement) == Runtime_Access_Result::unsupported_runtime_write);
|
||||||
|
REQUIRE(*device.value == 9);
|
||||||
|
REQUIRE(*replacement == 11);
|
||||||
|
}
|
||||||
static void test_runtime_access_uses_managed_synchronization() {
|
static void test_runtime_access_uses_managed_synchronization() {
|
||||||
Runtime_Device device;
|
Runtime_Device device;
|
||||||
Property_Object_Base& erased = device;
|
Property_Object_Base& erased = device;
|
||||||
@@ -134,6 +158,7 @@ static void test_runtime_read_only_fast_path() {
|
|||||||
}
|
}
|
||||||
int main() {
|
int main() {
|
||||||
test_runtime_metadata_and_results();
|
test_runtime_metadata_and_results();
|
||||||
|
test_runtime_copy_write_boundary();
|
||||||
test_runtime_access_uses_managed_synchronization();
|
test_runtime_access_uses_managed_synchronization();
|
||||||
test_runtime_read_only_fast_path();
|
test_runtime_read_only_fast_path();
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -97,6 +97,7 @@ A property descriptor exposes compile-time structural facts:
|
|||||||
using Property = std::remove_cvref_t<decltype(schema.property<&Device::temperature>())>;
|
using Property = std::remove_cvref_t<decltype(schema.property<&Device::temperature>())>;
|
||||||
static_assert(Property::readable);
|
static_assert(Property::readable);
|
||||||
static_assert(Property::writable);
|
static_assert(Property::writable);
|
||||||
|
static_assert(Property::runtime_copy_writable);
|
||||||
using Value = Property::value_type;
|
using Value = Property::value_type;
|
||||||
using Accessor = Property::accessor_type;
|
using Accessor = Property::accessor_type;
|
||||||
```
|
```
|
||||||
@@ -570,7 +571,7 @@ For a synchronized writable property, Structive keeps the corresponding managed
|
|||||||
auto result = erased.runtime_write("temperature", typeid(double), &value);
|
auto result = erased.runtime_write("temperature", typeid(double), &value);
|
||||||
```
|
```
|
||||||
|
|
||||||
Runtime write intentionally performs no implicit conversion. `typeid(double)` must exactly match the property's declared value type, and the pointer must address a live value of that exact type for the duration of the call. A successful runtime write uses the same managed write path and synchronization semantics as typed `write`.
|
Runtime write intentionally performs no implicit conversion. `typeid(double)` must exactly match the property's declared value type, and the pointer must address a live value of that exact type for the duration of the call. The boundary copies from a `const` input. If a writable accessor cannot accept that copy input, its descriptor exposes `runtime_copy_writable == false` and runtime access returns `unsupported_runtime_write`; typed `write` still accepts move-only values when the accessor supports them. A successful runtime write uses the same managed synchronization path as typed `write`.
|
||||||
|
|
||||||
### 22.3 Result contract
|
### 22.3 Result contract
|
||||||
|
|
||||||
@@ -580,6 +581,7 @@ Runtime write intentionally performs no implicit conversion. `typeid(double)` mu
|
|||||||
| `unknown_property` | no schema property has that runtime key |
|
| `unknown_property` | no schema property has that runtime key |
|
||||||
| `not_readable` | the property exists but its intrinsic capability is not readable |
|
| `not_readable` | the property exists but its intrinsic capability is not readable |
|
||||||
| `not_writable` | the property exists but its intrinsic capability is not writable |
|
| `not_writable` | the property exists but its intrinsic capability is not writable |
|
||||||
|
| `unsupported_runtime_write` | the property is intrinsically writable but its accessor cannot accept the runtime copy-input boundary |
|
||||||
| `type_mismatch` | runtime write supplied a type different from the declared property value type |
|
| `type_mismatch` | runtime write supplied a type different from the declared property value type |
|
||||||
|
|
||||||
There is no external/persistence access mode and no access-control policy in this API. An adapter decides whether it wants to expose or call runtime read/write; Structive reports only the property's intrinsic capability.
|
There is no external/persistence access mode and no access-control policy in this API. An adapter decides whether it wants to expose or call runtime read/write; Structive reports only the property's intrinsic capability.
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ Descriptor 暴露编译期结构事实:
|
|||||||
using Property = std::remove_cvref_t<decltype(schema.property<&Device::temperature>())>;
|
using Property = std::remove_cvref_t<decltype(schema.property<&Device::temperature>())>;
|
||||||
static_assert(Property::readable);
|
static_assert(Property::readable);
|
||||||
static_assert(Property::writable);
|
static_assert(Property::writable);
|
||||||
|
static_assert(Property::runtime_copy_writable);
|
||||||
using Value = Property::value_type;
|
using Value = Property::value_type;
|
||||||
using Accessor = Property::accessor_type;
|
using Accessor = Property::accessor_type;
|
||||||
```
|
```
|
||||||
@@ -562,7 +563,7 @@ auto result = erased.runtime_read("temperature", context, callback);
|
|||||||
auto result = erased.runtime_write("temperature", typeid(double), &value);
|
auto result = erased.runtime_write("temperature", typeid(double), &value);
|
||||||
```
|
```
|
||||||
|
|
||||||
Runtime write 明确不做隐式转换。`typeid(double)` 必须与 Property 声明的 value type 完全一致,`value` 指针在调用期间必须指向这个精确类型的有效对象。成功写入复用 typed `write` 的同一 managed write 和 synchronization 语义。
|
Runtime write 明确不做隐式转换。`typeid(double)` 必须与 Property 声明的 value type 完全一致,`value` 指针在调用期间必须指向这个精确类型的有效对象。这个边界从 `const` 输入复制;如果一个 intrinsically writable Accessor 无法接受 copy-input,它会暴露 `runtime_copy_writable == false`,runtime access 返回 `unsupported_runtime_write`,而 typed `write` 在 Accessor 支持时仍可正常接收 move-only 值。成功写入继续复用 typed `write` 的 managed synchronization 路径。
|
||||||
|
|
||||||
### 22.3 Result Contract
|
### 22.3 Result Contract
|
||||||
|
|
||||||
@@ -572,6 +573,7 @@ Runtime write 明确不做隐式转换。`typeid(double)` 必须与 Property 声
|
|||||||
| `unknown_property` | Schema 中不存在该 runtime key |
|
| `unknown_property` | Schema 中不存在该 runtime key |
|
||||||
| `not_readable` | Property 存在,但 intrinsic capability 不可读 |
|
| `not_readable` | Property 存在,但 intrinsic capability 不可读 |
|
||||||
| `not_writable` | Property 存在,但 intrinsic capability 不可写 |
|
| `not_writable` | Property 存在,但 intrinsic capability 不可写 |
|
||||||
|
| `unsupported_runtime_write` | Property intrinsically writable,但 Accessor 无法接收 runtime copy-input 边界 |
|
||||||
| `type_mismatch` | runtime write 提供的类型和 Property value type 不一致 |
|
| `type_mismatch` | runtime write 提供的类型和 Property value type 不一致 |
|
||||||
|
|
||||||
这里没有 external/persistence mode,也没有访问控制 policy。Adapter 自己决定是否对外暴露、是否调用 runtime read/write;Structive 只报告 Property 自身的 intrinsic capability。
|
这里没有 external/persistence mode,也没有访问控制 policy。Adapter 自己决定是否对外暴露、是否调用 runtime read/write;Structive 只报告 Property 自身的 intrinsic capability。
|
||||||
|
|||||||
+1
-1
@@ -298,7 +298,7 @@ Examples:
|
|||||||
- requesting a typed unique guard for a read-only property;
|
- requesting a typed unique guard for a read-only property;
|
||||||
- reading a write-only property.
|
- reading a write-only property.
|
||||||
|
|
||||||
Runtime key APIs report dynamic failures through `Runtime_Access_Result`.
|
Runtime key APIs report dynamic failures through `Runtime_Access_Result`. The runtime write boundary is copy-input by design: intrinsic `writable` remains a structural fact, while `runtime_copy_writable` states whether the accessor can participate in that type-erased copy boundary. Move-only typed writes therefore do not get mislabeled as structurally non-writable.
|
||||||
|
|
||||||
### Rule
|
### Rule
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ Adapter 自己决定是否暴露某个 Property、是否调用 runtime read/writ
|
|||||||
- 对 read-only property 请求 typed unique guard;
|
- 对 read-only property 请求 typed unique guard;
|
||||||
- 读取 write-only property。
|
- 读取 write-only property。
|
||||||
|
|
||||||
Runtime key API 才使用 `Runtime_Access_Result` 返回动态错误。
|
Runtime key API 才使用 `Runtime_Access_Result` 返回动态错误。Runtime write 明确是 copy-input 边界:intrinsic `writable` 继续表示结构事实,`runtime_copy_writable` 单独表示 Accessor 是否能参与 type-erased copy write。这样 move-only typed write 不会被错误描述成结构上不可写。
|
||||||
|
|
||||||
### 原则
|
### 原则
|
||||||
|
|
||||||
|
|||||||
+22
-9
@@ -1,18 +1,31 @@
|
|||||||
add_library(structive_property_extensions STATIC "${CMAKE_CURRENT_LIST_DIR}/src/presentation.cpp")
|
add_library(structive_property_extensions STATIC "${CMAKE_CURRENT_LIST_DIR}/src/presentation.cpp")
|
||||||
add_library(structive::property_extensions ALIAS structive_property_extensions)
|
add_library(structive::property_extensions ALIAS structive_property_extensions)
|
||||||
target_include_directories(structive_property_extensions PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include")
|
set_target_properties(structive_property_extensions PROPERTIES EXPORT_NAME property_extensions)
|
||||||
|
target_include_directories(structive_property_extensions PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
|
||||||
target_link_libraries(structive_property_extensions PUBLIC structive::property_core)
|
target_link_libraries(structive_property_extensions PUBLIC structive::property_core)
|
||||||
target_compile_features(structive_property_extensions PUBLIC cxx_std_20)
|
target_compile_features(structive_property_extensions PUBLIC cxx_std_20)
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
target_compile_options(structive_property_extensions PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(structive_property_extensions PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
endif ()
|
endif()
|
||||||
add_executable(structive_property_extensions_example "${CMAKE_CURRENT_LIST_DIR}/example/main.cpp")
|
if(STRUCTIVE_BUILD_EXAMPLES)
|
||||||
target_link_libraries(structive_property_extensions_example PRIVATE structive::property_extensions)
|
add_executable(structive_property_extensions_example "${CMAKE_CURRENT_LIST_DIR}/example/main.cpp")
|
||||||
if (BUILD_TESTING)
|
target_link_libraries(structive_property_extensions_example PRIVATE structive::property_extensions)
|
||||||
|
endif()
|
||||||
|
if(STRUCTIVE_BUILD_TESTS)
|
||||||
add_executable(structive_property_extensions_test "${CMAKE_CURRENT_LIST_DIR}/tests/presentation_test.cpp")
|
add_executable(structive_property_extensions_test "${CMAKE_CURRENT_LIST_DIR}/tests/presentation_test.cpp")
|
||||||
target_link_libraries(structive_property_extensions_test PRIVATE structive::property_extensions)
|
target_link_libraries(structive_property_extensions_test PRIVATE structive::property_extensions)
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
target_compile_options(structive_property_extensions_test PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(structive_property_extensions_test PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
endif ()
|
endif()
|
||||||
add_test(NAME structive_property_extensions_test COMMAND structive_property_extensions_test)
|
add_test(NAME structive_property_extensions_test COMMAND structive_property_extensions_test)
|
||||||
endif ()
|
set_tests_properties(structive_property_extensions_test PROPERTIES LABELS "unit;extensions")
|
||||||
|
set(header structive/property/extensions/presentation.hpp)
|
||||||
|
set(target structive_header_structive_property_extensions_presentation_hpp)
|
||||||
|
set(source "${CMAKE_CURRENT_BINARY_DIR}/structive_header_tests/${target}.cpp")
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/structive_header_tests")
|
||||||
|
file(WRITE "${source}" "#include <${header}>\nint main() {\n return 0;\n}\n")
|
||||||
|
add_executable("${target}" "${source}")
|
||||||
|
target_link_libraries("${target}" PRIVATE structive::property_extensions)
|
||||||
|
add_test(NAME "${target}" COMMAND "${target}")
|
||||||
|
set_tests_properties("${target}" PROPERTIES LABELS "header;extensions")
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(StructiveInstallConsumer LANGUAGES CXX)
|
||||||
|
include(CTest)
|
||||||
|
find_package(Structive CONFIG REQUIRED COMPONENTS Core Extensions)
|
||||||
|
add_executable(structive_install_consumer main.cpp)
|
||||||
|
target_link_libraries(structive_install_consumer PRIVATE structive::property_core structive::property_extensions)
|
||||||
|
target_compile_features(structive_install_consumer PRIVATE cxx_std_20)
|
||||||
|
add_test(NAME structive_install_consumer COMMAND structive_install_consumer)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include <structive/property/extensions/presentation.hpp>
|
||||||
|
#include <structive/property/property.hpp>
|
||||||
|
struct Consumer_Device : structive::Property_Object<Consumer_Device> {
|
||||||
|
int value{1};
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct structive::Type_Descriptor<Consumer_Device> {
|
||||||
|
static auto get() {
|
||||||
|
return object<Consumer_Device>(field<&Consumer_Device::value>(key<"value">, presentation::label<"Value">));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
Consumer_Device device;
|
||||||
|
device.write<&Consumer_Device::value>(2);
|
||||||
|
return device.read<&Consumer_Device::value>() == 2 ? 0 : 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user