diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c35872..589f77f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.20) project(Adminive VERSION 1.0.0 LANGUAGES CXX) include(CTest) +add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/third_party/Structive") add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/backend") set(adminive_package_includes "/.gitignore" @@ -15,6 +16,7 @@ set(adminive_package_includes ) set(adminive_package_excludes "/.git/" + "/third_party/Structive/.git/" "/.idea/" "/build/" "/cmake-build-*/" diff --git a/README.md b/README.md index 718c03e..474ee44 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Adminive Backend-Driven Complete Example -Adminive 使用 C++20 模板、Concepts 和可替换适配器描述业务类型。同一份后端描述驱动 JSON 双向转换、事务式赋值、字段权限、校验、amis CRUD、枚举列表列、树状配置、联动条件、日期控件、颜色控件和一次性提交。核心层不依赖具体 JSON 库、枚举反射库或结构体反射库。前端只读取 `/admin/amis` 并渲染后端返回的页面 JSON,不包含业务字段或业务判断。 +Adminive 使用 C++20 模板、Concepts 和可替换适配器描述业务类型。同一份后端描述驱动 JSON 双向转换、事务式赋值、前端可编辑性、校验、amis CRUD、枚举列表列、树状配置、联动条件、日期控件、颜色控件和一次性提交。属性能力、Schema 与同步拓扑由 `third_party/Structive` 提供,Adminive 只叠加后台管理领域的描述、JSON、HTTP、AMIS 和事务适配。核心层不依赖具体 JSON 库、枚举反射库或结构体反射库。前端只读取 `/admin/amis` 并渲染后端返回的页面 JSON,不包含业务字段或业务判断。 ## 后端分层 -`backend/library` 是库级协议层,只有头文件,不依赖也不链接任何具体 JSON、HTTP Server、枚举反射或结构体反射实现。它包含 descriptor、JSON 适配协议、AMIS schema、状态协议、同步协议和抽象 `Resource_Service`: +`backend/library` 是库级协议层,只有头文件,不依赖也不链接任何具体 JSON、HTTP Server、枚举反射或结构体反射实现。它依赖项目内的 `third_party/Structive`,由 Structive 负责 intrinsic property capability、Schema 和 synchronization;Adminive 自身包含 descriptor、JSON 适配协议、AMIS schema、状态协议、managed adapter 和抽象 `Resource_Service`: ```cmake target_link_libraries(your_target PRIVATE Adminive::Core) @@ -130,64 +130,98 @@ transaction.rollback = [](const Runtime_Config_Model& original, const adminive:: adminive::Resource_Service resource(runtime, "/config", std::move(transaction)); ``` -`Resource_Service` 对一次更新持有同一个一致性作用域,锁覆盖快照、补丁应用、`prepare`、运行时 `commit`、外部 `commit`、失败恢复和响应快照。绑定根 `Synchronized_Value` 时独占根 barrier;绑定 `Synchronized_Field` 子对象时默认只独占对应子对象节点并持有祖先共享 barrier,不会为了修改一个子配置锁死整个根配置。如果事务回调需要读取、复制或序列化整个根配置,则必须把资源作用域设为 `Resource_Lock_Scope::root`,让整个事务持有根独占 barrier。事务回调应保持短小;使用非递归锁时,回调不能再次进入同一个同步作用域,而应调用已经处于锁内的持久化实现。 +`Resource_Service` 绑定普通对象时继续使用调用方提供的 `Basic_Lock`;绑定 `Managed_Value` 或 `Managed_Field<...>` 时直接使用 Structive 的同步拓扑。一次更新的候选快照、补丁应用、`prepare`、运行时 `commit`、外部 `commit`、失败恢复和响应快照都发生在同一个 managed write 作用域内。Adminive 不再维护第二套根 barrier/字段锁,也不再提供 `Resource_Lock_Scope`。 ```cpp +adminive::Managed_Value config; auto radio = config.member<&Application_Config::radio_service>(); adminive::Resource_Service resource( radio, "/config/radio", - transaction, - adminive::Resource_Lock_Scope::root + transaction ); ``` -`local` 是子资源默认值,适用于事务只操作该子对象或对应数据库行的场景;`root` 只用于事务确实需要根对象一致性快照的场景,例如把整个 `Application_Config` 写入一个 JSON 配置文件。 +事务回调应保持短小。如果持久化事务需要整个根对象的一致性快照,应在该 `Managed_Value` 实例上使用 Structive 的共享同步拓扑,让所有 writable 根属性落入同一个 lock domain。示例 `Config_Store` 正是这样配置:文件持久化横跨整个 `Application_Config`,因此使用 `sync_all_shared`;普通资源默认仍使用独立属性锁,不会为了修改一个无关字段锁住所有可写字段。 -### 配置同步协议 +### Structive managed property 与同步 -持久化和进程内同步是两个独立问题。配置写入文件或数据库后,HTTP 线程、业务线程和后台任务仍可能同时访问运行时对象,因此动态字段仍需要进程内同步。Adminive 的默认策略是:`.editable()` 字段自动拥有字段锁;普通只读字段不拥有字段锁;`.locked()` 可以强制给不可编辑但会被程序动态修改的字段启用字段锁;`.locked(false)` 可以显式关闭某个可编辑字段的字段锁。 +Adminive 不再拥有独立的属性同步系统。字段描述只声明这个字段在 Adminive managed model 中是否可写以及是否需要同步,实际 property capability、lock slot、mutex 和 guard 都由 `third_party/Structive` 实现。 + +字段默认是 intrinsic read-only: ```cpp return adminive::object( "config", + ADMINIVE_FIELD(Config, immutable_value), ADMINIVE_FIELD(Config, editable_value).editable(), - ADMINIVE_FIELD(Config, startup_only_value), - ADMINIVE_FIELD(Config, backend_mutable_value).locked(), - ADMINIVE_FIELD(Config, no_dedicated_field_lock).editable().locked(false) + ADMINIVE_FIELD(Config, backend_mutable_value).read_write(), + ADMINIVE_FIELD(Config, externally_synchronized_value).read_write().unsynchronized() ); ``` -`Synchronized_Value` 内部同时维护根对象 barrier 和层级字段锁。所有字段读取至少持有根共享 barrier,保证它不会和整对象提交、替换或序列化事务并发;普通只读字段不再额外获取字段锁。可编辑或显式 `.locked()` 的字段再获取对应的层级字段锁,因此不同动态字段仍可以并发。`.locked(false)` 只关闭当前字段自己的专用字段锁;如果该字段是结构体且后代存在动态字段,后代所需的结构 barrier 仍然保留。对未启用字段锁的字段执行写操作时会直接升级为根独占操作,避免出现无保护写入。序列化、反序列化、对象级校验和整对象事务属于一致性操作,会独占被操作对象的 barrier,避免跨字段撕裂快照。嵌套对象序列化只独占该子对象节点,同时持有祖先共享锁,不会无条件锁死整个根配置。 +常用语义如下: + +- 普通 `ADMINIVE_FIELD(...)`:Structive intrinsic read-only。不会创建 lock slot,不贡献 mutex,managed read 直接走 Structive 的零锁路径。 +- `.editable()`:字段 intrinsic read-write,同时允许前端 update。默认进入 Structive synchronization topology。 +- `.creatable()`:字段 intrinsic read-write,同时允许 create 输入。 +- `.read_write()`:字段 intrinsic read-write,但不因此对前端开放编辑;适合后台代码、配置加载或其他受控路径修改。 +- `.unsynchronized()`:保持字段 intrinsic writable,但明确不为它创建 Structive mutex;仅应在字段自身原子化、外部已有更高层同步或生命周期保证单线程时使用。 + +`readable()/editable()/creatable()/sensitive()` 等仍是 Adminive 的领域/UI 元数据,不是 Structive 的访问控制。Structive 不决定 HTTP、GUI、RPC 或其他外部系统是否应该暴露某字段;Adminive 的具体适配器根据自己的元数据和业务场景决定。 + +`Managed_Value` 是 Structive `Property_Object` 的 Adminive 适配对象,`Managed_Field` 是一个轻量字段路径代理: ```cpp -adminive::Synchronized_Value config; +adminive::Managed_Value config; auto radio = config.member<&Application_Config::radio_service>(); auto worker_count = radio.member<&Radio_Service_Config::worker_count>(); worker_count.write([](auto& value) { value = 8; }); -auto snapshot = radio.snapshot(); +auto radio_snapshot = radio.snapshot(); auto root_snapshot = config.snapshot(); +``` -adminive::Synchronized_Value reflected_config; +反射字段同样使用 `field()`: + +```cpp +adminive::Managed_Value reflected_config; reflected_config.field<0>().write([](auto& value) { value = 8; }); ``` -描述协议 `adminive.resource` 版本为 3。每个字段会输出 `lock_mode` 和最终计算后的 `synchronized`,因此服务层能够明确知道该字段的并发语义,而不是由具体 JSON 或 HTTP 后端隐式决定。 +只读字段的优化来自 Structive 本身,而不是 Adminive 的运行时判断。对于 intrinsic read-only 存储属性,解析后的 slot 为 `unsynchronized_slot`,它不会增加 `lock_count`,也不会创建 mutex;因此“给字段添加 read-only 信息”会直接改变对象的同步布局和热路径成本。嵌套路径同样利用这条信息:如果父字段自身不是 writable,读取其中的 intrinsic read-only 子字段不会因为同级存在可写字段而获取父锁;`.unsynchronized()` 子字段的 managed write 也不会获取该父锁。如果某个祖先字段自身声明为 writable,则它允许整体替换整个子树,该祖先的同步语义自然覆盖后代访问。直接绕过 managed API 修改原始成员属于 raw C++ path,同时也绕过 Structive 的同步保证。 -数据库事务不能替代这套运行时锁。`Resource_Service` 在一次 HTTP 更新中持有对应资源的一致性锁作用域,覆盖候选快照、补丁应用、对象校验、运行时提交、持久化回调、失败回滚和响应快照。数据库负责持久化原子性;对象/字段 barrier 负责当前进程中的运行时一致性。 +`Managed_Value::read/write` 和 `Managed_Field::read/write` 的回调结果不能把受保护对象的引用生命周期带出同步作用域。接口在编译期拒绝引用、指针、`reference_wrapper` 和 ranges view 返回值;需要把数据带出时应返回值副本或使用 `snapshot()`。 -如果对象生命周期已经由外部严格保证为单线程,可以统一换成 `Empty_Lock`,不需要维护另一套无锁实现: +默认 writable 字段使用独立同步域。Adminive 的 `.unsynchronized()` 会把对应 Structive property 明确设为无同步;需要跨多个根属性保持一致性的特殊实例,可以直接传入 Structive `Property_Synchronization`: ```cpp -adminive::Synchronized_Value config; -adminive::Resource_Service resource(config, "/config"); +auto synchronization = structive::property_synchronization( + structive::Synchronization_Plan{structive::Synchronization_Default::shared} +); +adminive::Managed_Value config(std::move(synchronization)); ``` -同步对象不公开原始值和 mutex。普通业务代码只能通过 `read()`、`write()`、`snapshot()`、`replace()`、显式成员的 `member<&T::x>()` 和反射字段的 `field()` 进入同步协议,避免重新出现 `data() + mutex()` 由调用方手工配对的问题。同步回调会静态拒绝引用、指针、`reference_wrapper` 和 ranges view 等常见引用逃逸形式;自定义返回类型如果内部保存受保护对象的地址或引用,仍由调用方保证不会把它带出锁作用域。 +这也是 `Config_Store` 的选择,因为一次文件提交需要读取完整根配置。对于一般内存对象不应无条件使用 shared topology,否则会把本来可以并发的 writable 字段收敛到同一把锁。 + +如果对象生命周期已经由外部严格保证为单线程,可以使用 Structive 的 `No_Lock_Policy`: + +```cpp +adminive::Managed_Value config; +``` + +如果需要使用自定义 shared-lockable mutex,则使用 Adminive 的薄适配器: + +```cpp +adminive::Managed_Value> config; +``` + +描述协议 `adminive.resource` 当前版本为 4。字段输出 `writable` 与最终 `synchronized` 信息,不再包含旧的 `lock_mode`。`writable` 表示 Adminive managed model 的 intrinsic mutability;`synchronized` 只表示该 writable 字段是否贡献 Structive 同步域,与前端是否 editable 是两个独立维度。 + +数据库事务不能替代运行时同步。数据库负责持久化原子性;Structive synchronization 负责当前进程中的 mutable consistency;`Resource_Transaction` 负责 Adminive 更新过程中外部副作用的 prepare/commit/rollback。三者职责保持独立。 ### 外部控件适配 diff --git a/backend/library/CMakeLists.txt b/backend/library/CMakeLists.txt index 9c5a36c..20d4f28 100644 --- a/backend/library/CMakeLists.txt +++ b/backend/library/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(Adminive::Core ALIAS Adminive) add_library(Adminive::Http ALIAS Adminive) target_include_directories(Adminive INTERFACE "$" "$") target_compile_features(Adminive INTERFACE cxx_std_20) +target_link_libraries(Adminive INTERFACE "$") if(MSVC) target_compile_options(Adminive INTERFACE /utf-8 /Zc:__cplusplus) if(ADMINIVE_PROPAGATE_MSVC_STRICT_MODE) @@ -22,3 +23,4 @@ if(BUILD_TESTING) endif() install(TARGETS Adminive EXPORT AdminiveCoreTargets) install(DIRECTORY "${adminive_library_include_dir}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../../third_party/Structive/core/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") diff --git a/backend/library/include/adminive/adminive.hpp b/backend/library/include/adminive/adminive.hpp index b51ad8a..0c37047 100644 --- a/backend/library/include/adminive/adminive.hpp +++ b/backend/library/include/adminive/adminive.hpp @@ -7,5 +7,5 @@ #include "adminive/http.hpp" #include "adminive/json.hpp" #include "adminive/status.hpp" -#include "adminive/synchronized.hpp" +#include "adminive/managed.hpp" #include "adminive/value.hpp" diff --git a/backend/library/include/adminive/amis.hpp b/backend/library/include/adminive/amis.hpp index e29e9be..1bb5d09 100644 --- a/backend/library/include/adminive/amis.hpp +++ b/backend/library/include/adminive/amis.hpp @@ -459,15 +459,15 @@ Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::strin } return result; } -template -Json to_amis_form_schema(const Synchronized_Value& value, std::string submit_api = {}, std::string submit_label = "Apply") { - return value.read([&](const T& item) { +template +Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::string submit_label = "Apply") { + return value.read([&](const auto& item) { return adminive::to_amis_form_schema(item, std::move(submit_api), std::move(submit_label)); }); } -template -Json to_amis_form_schema(const Synchronized_Field& value, std::string submit_api = {}, std::string submit_label = "Apply") { - return value.object_read([&](const auto& item) { +template +Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::string submit_label = "Apply") { + return value.read([&](const auto& item) { return adminive::to_amis_form_schema(item, std::move(submit_api), std::move(submit_label)); }); } diff --git a/backend/library/include/adminive/descriptor.hpp b/backend/library/include/adminive/descriptor.hpp index 1c53be4..fac3721 100644 --- a/backend/library/include/adminive/descriptor.hpp +++ b/backend/library/include/adminive/descriptor.hpp @@ -12,11 +12,6 @@ #include #include namespace adminive { -enum class Field_Lock_Mode { - automatic, - enabled, - disabled -}; template struct Type_Descriptor; template @@ -39,6 +34,7 @@ template struct Member_Field_Accessor { using owner_type = typename Member_Pointer_Traits::owner_type; using member_type = typename Member_Pointer_Traits::member_type; + static constexpr auto member = Member; template static decltype(auto) get(Object& object) noexcept { return object.*Member; @@ -54,6 +50,7 @@ struct Reflected_Field_Accessor { using reference_type = decltype(Reflection_Adapter::template get(std::declval())); using const_reference_type = decltype(Reflection_Adapter::template get(std::declval())); using member_type = std::remove_cvref_t; + static constexpr std::size_t index = Index; static_assert(std::is_lvalue_reference_v); static_assert(std::is_lvalue_reference_v); static decltype(auto) get(T& object) noexcept(noexcept(Reflection_Adapter::template get(object))) { @@ -83,8 +80,8 @@ inline std::string make_label(std::string_view name) { std::string result; result.reserve(name.size()); bool uppercase = true; - for (const char value : name) { - if (value == '_') { + for(const char value : name) { + if(value == '_') { result.push_back(' '); uppercase = true; continue; @@ -94,111 +91,142 @@ inline std::string make_label(std::string_view name) { } return result; } -template -class Field_Descriptor_Base { +struct Field_Metadata { + std::string name; + std::string label; + std::string list_label; + std::string description; + std::string widget; + std::string visible_on; + std::map enum_labels; + bool editable{}; + bool creatable{}; + bool readable{true}; + bool sensitive{}; + bool include_default{true}; + bool visible{true}; + bool required{}; + bool list_visible{true}; + int order{-1}; + bool sortable{}; +}; +template +class Field_Descriptor { public: + using field_descriptor_tag = void; + using accessor_type = Accessor; using owner_type = typename Accessor::owner_type; using member_type = typename Accessor::member_type; - explicit Field_Descriptor_Base(std::string name) : name_(std::move(name)), label_(make_label(name_)) {} - Field_Descriptor_Base(std::string name, std::string label) : name_(std::move(name)), label_(std::move(label)) {} - Derived editable(bool value = true) const { - auto result = derived(); - result.editable_ = value; + static constexpr bool managed_writable = Managed_Writable; + static constexpr bool synchronized = Synchronized; + explicit Field_Descriptor(std::string name) { + metadata_.name = std::move(name); + metadata_.label = make_label(metadata_.name); + } + Field_Descriptor(std::string name, std::string label) { + metadata_.name = std::move(name); + metadata_.label = std::move(label); + } + explicit Field_Descriptor(Field_Metadata metadata) : metadata_(std::move(metadata)) {} + auto editable() const { + auto result = rebind(); + result.metadata_.editable = true; return result; } - Derived locked(bool value = true) const { - auto result = derived(); - result.lock_mode_ = value ? Field_Lock_Mode::enabled : Field_Lock_Mode::disabled; + auto creatable() const { + auto result = rebind(); + result.metadata_.creatable = true; return result; } - Derived creatable(bool value = true) const { - auto result = derived(); - result.creatable_ = value; + auto read_write() const { + return rebind(); + } + auto unsynchronized() const { + return rebind(); + } + Field_Descriptor readable(bool value = true) const { + auto result = *this; + result.metadata_.readable = value; return result; } - Derived readable(bool value = true) const { - auto result = derived(); - result.readable_ = value; - return result; - } - Derived sensitive(bool value = true) const { - auto result = derived(); - result.sensitive_ = value; + Field_Descriptor sensitive(bool value = true) const { + auto result = *this; + result.metadata_.sensitive = value; if(value) { - result.readable_ = false; - result.include_default_ = false; + result.metadata_.readable = false; + result.metadata_.include_default = false; } return result; } - Derived include_default(bool value = true) const { - auto result = derived(); - result.include_default_ = value; + Field_Descriptor include_default(bool value = true) const { + auto result = *this; + result.metadata_.include_default = value; return result; } - Derived visible(bool value = true) const { - auto result = derived(); - result.visible_ = value; + Field_Descriptor visible(bool value = true) const { + auto result = *this; + result.metadata_.visible = value; return result; } - Derived required(bool value = true) const { - auto result = derived(); - result.required_ = value; + Field_Descriptor required(bool value = true) const { + auto result = *this; + result.metadata_.required = value; return result; } - Derived list_visible(bool value = true) const { - auto result = derived(); - result.list_visible_ = value; + Field_Descriptor list_visible(bool value = true) const { + auto result = *this; + result.metadata_.list_visible = value; return result; } - Derived label(std::string value) const { - auto result = derived(); - result.label_ = std::move(value); + Field_Descriptor label(std::string value) const { + auto result = *this; + result.metadata_.label = std::move(value); return result; } - Derived description(std::string value) const { - auto result = derived(); - result.description_ = std::move(value); + Field_Descriptor description(std::string value) const { + auto result = *this; + result.metadata_.description = std::move(value); return result; } - Derived widget(std::string value) const { - auto result = derived(); - result.widget_ = std::move(value); + Field_Descriptor widget(std::string value) const { + auto result = *this; + result.metadata_.widget = std::move(value); return result; } - Derived visible_on(std::string value) const { - auto result = derived(); - result.visible_on_ = std::move(value); + Field_Descriptor visible_on(std::string value) const { + auto result = *this; + result.metadata_.visible_on = std::move(value); return result; } - Derived list_label(std::string value) const { - auto result = derived(); - result.list_label_ = std::move(value); + Field_Descriptor list_label(std::string value) const { + auto result = *this; + result.metadata_.list_label = std::move(value); return result; } - Derived order(int value) const { - auto result = derived(); - result.order_ = value; + Field_Descriptor order(int value) const { + auto result = *this; + result.metadata_.order = value; return result; } - Derived sortable(bool value = true) const { - auto result = derived(); - result.sortable_ = value; + Field_Descriptor sortable(bool value = true) const { + auto result = *this; + result.metadata_.sortable = value; return result; } template requires Enum_Type> - Derived enum_label(std::string label) const { + Field_Descriptor enum_label(std::string label) const { const auto name = enum_name(Value); - if (name.empty()) { + if(name.empty()) { throw std::invalid_argument("enum adapter returned an empty value name"); } - auto result = derived(); - result.enum_labels_.insert_or_assign(std::string(name), std::move(label)); + auto result = *this; + result.metadata_.enum_labels.insert_or_assign(std::string(name), std::move(label)); return result; } - Derived enum_label(std::string value_name, std::string label) const { - auto result = derived(); - result.enum_labels_.insert_or_assign(std::move(value_name), std::move(label)); + Field_Descriptor enum_label(std::string value_name, std::string label) const { + auto result = *this; + result.metadata_.enum_labels.insert_or_assign(std::move(value_name), std::move(label)); return result; } template @@ -212,112 +240,81 @@ public: return Accessor::get(object); } const std::string& name() const noexcept { - return name_; + return metadata_.name; } const std::string& label() const noexcept { - return label_; + return metadata_.label; } const std::string& list_label() const noexcept { - return list_label_.empty() ? label_ : list_label_; + return metadata_.list_label.empty() ? metadata_.label : metadata_.list_label; } const std::string& description() const noexcept { - return description_; + return metadata_.description; } const std::string& widget() const noexcept { - return widget_; + return metadata_.widget; } const std::string& visible_on() const noexcept { - return visible_on_; + return metadata_.visible_on; } const std::map& enum_labels() const noexcept { - return enum_labels_; + return metadata_.enum_labels; } bool is_editable() const noexcept { - return editable_; - } - Field_Lock_Mode lock_mode() const noexcept { - return lock_mode_; + return metadata_.editable; } bool is_creatable() const noexcept { - return creatable_; + return metadata_.creatable; } bool is_readable() const noexcept { - return readable_; + return metadata_.readable; } bool is_sensitive() const noexcept { - return sensitive_; + return metadata_.sensitive; } bool includes_default() const noexcept { - return include_default_; + return metadata_.include_default; } bool is_visible() const noexcept { - return visible_; + return metadata_.visible; } bool is_required() const noexcept { - return required_; + return metadata_.required; } int order() const noexcept { - return order_; + return metadata_.order; } bool is_sortable() const noexcept { - return sortable_; + return metadata_.sortable; } bool is_list_visible() const noexcept { - return list_visible_; - } -protected: - const Derived& derived() const noexcept { - return static_cast(*this); + return metadata_.list_visible; } private: - std::string name_; - std::string label_; - std::string list_label_; - std::string description_; - std::string widget_; - std::string visible_on_; - std::map enum_labels_; - bool editable_{}; - Field_Lock_Mode lock_mode_{Field_Lock_Mode::automatic}; - bool creatable_{}; - bool readable_{true}; - bool sensitive_{}; - bool include_default_{true}; - bool visible_{true}; - bool required_{}; - bool list_visible_{true}; - int order_{-1}; - bool sortable_{}; -}; -template -class Field_Descriptor : public Field_Descriptor_Base, Member_Field_Accessor> { -public: - using Base = Field_Descriptor_Base, Member_Field_Accessor>; - using Base::Base; - static constexpr auto member = Member; -}; -template -class Reflected_Field_Descriptor : public Field_Descriptor_Base, Reflected_Field_Accessor> { -public: - using Base = Field_Descriptor_Base, Reflected_Field_Accessor>; - using Base::Base; - static constexpr std::size_t index = Index; + template + auto rebind() const { + return Field_Descriptor(metadata_); + } + template + friend class Field_Descriptor; + Field_Metadata metadata_; }; template -struct Is_Field_Descriptor : std::false_type {}; -template -struct Is_Field_Descriptor> : std::true_type {}; -template -struct Is_Field_Descriptor> : std::true_type {}; -template -concept Field_Descriptor_Type = Is_Field_Descriptor>::value; +concept Field_Descriptor_Type = requires { + typename std::remove_cvref_t::field_descriptor_tag; + typename std::remove_cvref_t::owner_type; + typename std::remove_cvref_t::member_type; + typename std::remove_cvref_t::accessor_type; + { std::remove_cvref_t::managed_writable } -> std::convertible_to; + { std::remove_cvref_t::synchronized } -> std::convertible_to; +}; template auto field(std::string name) { - return Field_Descriptor(std::move(name)); + return Field_Descriptor>(std::move(name)); } template auto field(std::string name, std::string label) { - return Field_Descriptor(std::move(name), std::move(label)); + return Field_Descriptor>(std::move(name), std::move(label)); } template auto reflected_field() { @@ -326,7 +323,7 @@ auto reflected_field() { if(name.empty()) { throw std::invalid_argument("reflected field name must not be empty"); } - return Reflected_Field_Descriptor(name); + return Field_Descriptor>(name); } template auto reflected_field(std::string label) { @@ -335,7 +332,7 @@ auto reflected_field(std::string label) { if(name.empty()) { throw std::invalid_argument("reflected field name must not be empty"); } - return Reflected_Field_Descriptor(name, std::move(label)); + return Field_Descriptor>(name, std::move(label)); } struct No_Object_Validator { template @@ -361,6 +358,7 @@ class Object_Descriptor { public: using object_type = T; using validator_type = Validator; + using fields_type = std::tuple; Object_Descriptor(std::string name, std::string label, Validator validator, std::tuple fields, Object_List_Options list_options = {}) : name_(std::move(name)), label_(std::move(label)), validator_(std::move(validator)), fields_(std::move(fields)), list_options_(std::move(list_options)) {} template auto validator(New_Validator value) const { @@ -538,6 +536,32 @@ auto describe() { validate_descriptor(descriptor); return descriptor; } +template +using Descriptor_Type = std::remove_cvref_t>::get())>; +template +using Descriptor_Fields = typename Descriptor_Type::fields_type; +template +using Descriptor_Field = std::tuple_element_t>; +template +consteval bool described_type_has_managed_writes(); +template +consteval bool field_has_managed_writes() { + if constexpr(Field::managed_writable) { + return true; + } else if constexpr(Described_Type) { + return described_type_has_managed_writes(); + } else { + return false; + } +} +template +consteval bool described_type_has_managed_writes_impl(std::index_sequence) { + return (field_has_managed_writes>() || ...); +} +template +consteval bool described_type_has_managed_writes() { + return described_type_has_managed_writes_impl(std::make_index_sequence>>{}); +} } #define ADMINIVE_FIELD(Type, Member) ::adminive::field<&Type::Member>(#Member) #define ADMINIVE_FIELD_LABEL(Type, Member, Label) ::adminive::field<&Type::Member>(#Member, Label) diff --git a/backend/library/include/adminive/http.hpp b/backend/library/include/adminive/http.hpp index 7344fea..db64dad 100644 --- a/backend/library/include/adminive/http.hpp +++ b/backend/library/include/adminive/http.hpp @@ -1,12 +1,12 @@ #pragma once #include "adminive/amis.hpp" -#include "adminive/synchronized.hpp" +#include "adminive/lock.hpp" +#include "adminive/managed.hpp" #include #include #include #include #include -#include #include #include namespace adminive { @@ -50,10 +50,6 @@ template Http_Response make_http_error(int status, std::string message) { return Http_Response{status, make_http_result(status, std::move(message), json_object())}; } -enum class Resource_Lock_Scope { - local, - root -}; template struct Resource_Transaction { using Prepare_Function = std::function; @@ -69,7 +65,7 @@ Resource_Transaction make_resource_transaction(Function&& function) { result.commit = std::forward(function); return result; } -template +template requires Writable_Adapted_Object && std::copy_constructible> class Resource_Service { public: @@ -88,8 +84,8 @@ public: configure_raw_access(value, *owned_lock_); } } - template - explicit Resource_Service(Synchronized_Value& value, std::string path, Transaction transaction = {}) : path_(std::move(path)), transaction_(std::move(transaction)) { + template + explicit Resource_Service(Managed_Value& value, std::string path, Transaction transaction = {}) : path_(std::move(path)), transaction_(std::move(transaction)) { read_access_ = [&value](const Read_Function& function) { value.read([&](const Object& object) { function(object); @@ -101,20 +97,16 @@ public: }); }; } - template - requires (!std::is_const_v) && std::same_as::value_type, Object> - explicit Resource_Service(Synchronized_Field value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : path_(std::move(path)), transaction_(std::move(transaction)) { - if(lock_scope == Resource_Lock_Scope::root) { - configure_raw_access(detail::Synchronized_Access::value(value), detail::Synchronized_Access::mutex(value)); - return; - } + template + requires std::same_as::value_type, Object> && (!std::is_const_v) + explicit Resource_Service(Managed_Field value, std::string path, Transaction transaction = {}) : path_(std::move(path)), transaction_(std::move(transaction)) { read_access_ = [value](const Read_Function& function) { - value.object_read([&](const Object& object) { + value.read([&](const Object& object) { function(object); }); }; write_access_ = [value](const Write_Function& function) mutable { - value.object_write([&](Object& object) { + value.write([&](Object& object) { function(object); }); }; diff --git a/backend/library/include/adminive/json.hpp b/backend/library/include/adminive/json.hpp index 9a8a73c..196fc85 100644 --- a/backend/library/include/adminive/json.hpp +++ b/backend/library/include/adminive/json.hpp @@ -1,7 +1,7 @@ #pragma once #include "adminive/adapter.hpp" #include "adminive/descriptor.hpp" -#include "adminive/synchronized.hpp" +#include "adminive/managed.hpp" #include #include #include @@ -103,10 +103,10 @@ template requires Creatable_Adapted_Object Json to_descriptor_json_with_defaults(); template -requires Described_Type> || Object_Adapter_With_Snapshot +requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_json(const T& value); template -requires Described_Type> || Object_Adapter_With_Snapshot +requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_frontend_json(const T& value); template Update_Result assign_json(T& target, const Json& value); @@ -413,14 +413,9 @@ Json model_descriptor_json(const Model* defaults = nullptr) { json_set(field_json, "readable", item.is_readable()); json_set(field_json, "sensitive", item.is_sensitive()); json_set(field_json, "editable", item.is_editable()); - const char* lock_mode = "automatic"; - if(item.lock_mode() == Field_Lock_Mode::enabled) { - lock_mode = "enabled"; - } else if(item.lock_mode() == Field_Lock_Mode::disabled) { - lock_mode = "disabled"; - } - json_set(field_json, "lock_mode", lock_mode); - json_set(field_json, "synchronized", descriptor_field_requires_lock(item)); + using Field = std::remove_cvref_t; + json_set(field_json, "writable", Field::managed_writable); + json_set(field_json, "synchronized", Field::managed_writable && Field::synchronized); json_set(field_json, "creatable", item.is_creatable()); json_set(field_json, "visible", item.is_visible()); json_set(field_json, "required", item.is_required()); @@ -467,7 +462,7 @@ Json model_descriptor_json(const Model* defaults = nullptr) { json_set(list, "column_reorderable", options.column_reorderable_); Json result = json_object(); json_set(result, "protocol", "adminive.resource"); - json_set(result, "protocol_version", 3); + json_set(result, "protocol_version", 4); json_set(result, "name", descriptor.name()); json_set(result, "label", descriptor.label()); json_set(result, "value_type", "object"); @@ -506,7 +501,7 @@ Json model_to_json(const T& value, bool frontend) { return result; } template -requires Described_Type> || Object_Adapter_With_Snapshot +requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_json(const T& value) { using Object = std::remove_cvref_t; if constexpr(std::same_as>) { @@ -516,7 +511,7 @@ Json to_json(const T& value) { } } template -requires Described_Type> || Object_Adapter_With_Snapshot +requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_frontend_json(const T& value) { using Object = std::remove_cvref_t; if constexpr(std::same_as>) { @@ -526,44 +521,48 @@ Json to_frontend_json(const T& value) { } } template -requires Described_Type> || Object_Adapter_With_Snapshot +requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Json to_data_json(const T& value) { return to_frontend_json(value); } -template -Json to_json(const Synchronized_Value& value) { - return value.read([](const T& item) { +template +Json to_json(const T& value) { + return value.read([](const auto& item) { return adminive::to_json(item); }); } -template -Json to_frontend_json(const Synchronized_Value& value) { - return value.read([](const T& item) { +template +Json to_frontend_json(const T& value) { + return value.read([](const auto& item) { return adminive::to_frontend_json(item); }); } -template -Json to_data_json(const Synchronized_Value& value) { +template +Json to_data_json(const T& value) { return to_frontend_json(value); } -template -Json to_json(const Synchronized_Field& value) { - return value.object_read([](const auto& item) { +template +Json to_json(const T& value) { + return value.read([](const auto& item) { return adminive::to_json(item); }); } -template -Json to_frontend_json(const Synchronized_Field& value) { - return value.object_read([](const auto& item) { +template +Json to_frontend_json(const T& value) { + return value.read([](const auto& item) { return adminive::to_frontend_json(item); }); } -template -Json to_data_json(const Synchronized_Field& value) { +template +Json to_data_json(const T& value) { return to_frontend_json(value); } template bool field_writable(const Field& field, Write_Mode mode) { + using Descriptor = std::remove_cvref_t; + if constexpr(!Descriptor::managed_writable) { + return false; + } if(mode == Write_Mode::internal) { return true; } @@ -733,70 +732,70 @@ template Update_Result apply_patch(T& target, const Json& patch) { return apply_frontend_patch(target, patch); } -template -requires Writable_Adapted_Object -Update_Result assign_json(Synchronized_Value& target, const Json& value) { - return target.write([&](T& item) { +template +requires Writable_Adapted_Object::value_type> +Update_Result assign_json(T& target, const Json& value) { + return target.write([&](auto& item) { return adminive::assign_json(item, value); }); } -template -requires Writable_Adapted_Object -Update_Result apply_json_patch(Synchronized_Value& target, const Json& patch) { - return target.write([&](T& item) { +template +requires Writable_Adapted_Object::value_type> +Update_Result apply_json_patch(T& target, const Json& patch) { + return target.write([&](auto& item) { return adminive::apply_json_patch(item, patch); }); } -template -requires Creatable_Writable_Object -Update_Result apply_frontend_create(Synchronized_Value& target, const Json& value) { - return target.write([&](T& item) { +template +requires Creatable_Writable_Object::value_type> +Update_Result apply_frontend_create(T& target, const Json& value) { + return target.write([&](auto& item) { return adminive::apply_frontend_create(item, value); }); } -template -requires Writable_Adapted_Object -Update_Result apply_frontend_patch(Synchronized_Value& target, const Json& patch) { - return target.write([&](T& item) { +template +requires Writable_Adapted_Object::value_type> +Update_Result apply_frontend_patch(T& target, const Json& patch) { + return target.write([&](auto& item) { return adminive::apply_frontend_patch(item, patch); }); } -template -requires Writable_Adapted_Object -Update_Result apply_patch(Synchronized_Value& target, const Json& patch) { +template +requires Writable_Adapted_Object::value_type> +Update_Result apply_patch(T& target, const Json& patch) { return apply_frontend_patch(target, patch); } -template -requires (!std::is_const_v) && Writable_Adapted_Object::value_type> -Update_Result assign_json(Synchronized_Field target, const Json& value) { - return target.object_write([&](auto& item) { +template +requires Writable_Adapted_Object::value_type> +Update_Result assign_json(T target, const Json& value) { + return target.write([&](auto& item) { return adminive::assign_json(item, value); }); } -template -requires (!std::is_const_v) && Writable_Adapted_Object::value_type> -Update_Result apply_json_patch(Synchronized_Field target, const Json& patch) { - return target.object_write([&](auto& item) { +template +requires Writable_Adapted_Object::value_type> +Update_Result apply_json_patch(T target, const Json& patch) { + return target.write([&](auto& item) { return adminive::apply_json_patch(item, patch); }); } -template -requires (!std::is_const_v) && Creatable_Writable_Object::value_type> -Update_Result apply_frontend_create(Synchronized_Field target, const Json& value) { - return target.object_write([&](auto& item) { +template +requires Creatable_Writable_Object::value_type> +Update_Result apply_frontend_create(T target, const Json& value) { + return target.write([&](auto& item) { return adminive::apply_frontend_create(item, value); }); } -template -requires (!std::is_const_v) && Writable_Adapted_Object::value_type> -Update_Result apply_frontend_patch(Synchronized_Field target, const Json& patch) { - return target.object_write([&](auto& item) { +template +requires Writable_Adapted_Object::value_type> +Update_Result apply_frontend_patch(T target, const Json& patch) { + return target.write([&](auto& item) { return adminive::apply_frontend_patch(item, patch); }); } -template -requires (!std::is_const_v) && Writable_Adapted_Object::value_type> -Update_Result apply_patch(Synchronized_Field target, const Json& patch) { +template +requires Writable_Adapted_Object::value_type> +Update_Result apply_patch(T target, const Json& patch) { return apply_frontend_patch(target, patch); } template @@ -810,7 +809,7 @@ T from_json(const Json& value) { return result; } template -requires Described_Type> || Object_Adapter_With_Snapshot +requires (Described_Type> || Object_Adapter_With_Snapshot) && (!Managed_Value_Type) && (!Managed_Field_Type) Update_Result validate(const T& value) { Update_Result result; try { diff --git a/backend/library/include/adminive/lock.hpp b/backend/library/include/adminive/lock.hpp new file mode 100644 index 0000000..84446bc --- /dev/null +++ b/backend/library/include/adminive/lock.hpp @@ -0,0 +1,16 @@ +#pragma once +#include +namespace adminive { +struct No_Lock { + void lock() noexcept {} + bool try_lock() noexcept { + return true; + } + void unlock() noexcept {} +}; +template +concept Basic_Lock = std::default_initializable && requires(T& value) { + value.lock(); + value.unlock(); +}; +} diff --git a/backend/library/include/adminive/managed.hpp b/backend/library/include/adminive/managed.hpp new file mode 100644 index 0000000..5995e21 --- /dev/null +++ b/backend/library/include/adminive/managed.hpp @@ -0,0 +1,400 @@ +#pragma once +#include "adminive/descriptor.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace adminive { +template +struct Mutex_Policy { + using mutex_type = Mutex; +}; +template +class Managed_Value; +template +class Managed_Field; +namespace detail { +template +struct Is_Reference_Wrapper : std::false_type {}; +template +struct Is_Reference_Wrapper> : std::true_type {}; +template +concept Managed_Callback_Result = std::is_void_v || (!std::is_reference_v && !std::is_pointer_v && !Is_Reference_Wrapper>::value && !std::ranges::view>); +template +consteval std::size_t decimal_digits() { + std::size_t result = 1; + std::size_t current = Value; + while(current >= 10) { + current /= 10; + ++result; + } + return result; +} +template +consteval auto make_managed_key_text() { + constexpr std::string_view prefix = "__adminive_"; + constexpr std::size_t digits = decimal_digits(); + std::array result{}; + for(std::size_t position = 0; position < prefix.size(); ++position) { + result[position] = prefix[position]; + } + std::size_t value = Index; + for(std::size_t position = 0; position < digits; ++position) { + result[prefix.size() + digits - position - 1] = static_cast('0' + value % 10); + value /= 10; + } + return result; +} +template +struct Managed_Key_Value { + static constexpr auto text = make_managed_key_text(); + constexpr std::string_view view() const noexcept { + return {text.data(), text.size()}; + } +}; +template +struct Managed_Key_Attribute { + using attribute_category = structive::Key_Category; + static constexpr bool single_valued = true; + static constexpr bool inheritable = false; + static constexpr Managed_Key_Value value{}; +}; +template +consteval bool member_type_has_managed_writes() { + using Value = std::remove_cvref_t; + if constexpr(Described_Type) { + return described_type_has_managed_writes(); + } + return false; +} +template +inline constexpr bool managed_field_writable_v = Field::managed_writable || member_type_has_managed_writes(); +template +consteval bool field_requires_synchronization(); +template +consteval bool member_type_requires_synchronization() { + using Value = std::remove_cvref_t; + if constexpr(Described_Type) { + using Fields = Descriptor_Fields; + return [](std::index_sequence) { + return (field_requires_synchronization>() || ...); + }(std::make_index_sequence>{}); + } + return false; +} +template +consteval bool field_requires_synchronization() { + if constexpr(Field::managed_writable) { + return Field::synchronized; + } + return member_type_requires_synchronization(); +} +template +struct Managed_Field_Accessor { + using object_type = Managed; + using value_type = typename Field::member_type; + struct storage_identity {}; + static constexpr bool readable = true; + static constexpr bool writable = managed_field_writable_v; + static constexpr bool synchronized_view_read = false; + static constexpr bool trusted_object_access = false; + const value_type& read(const object_type& object) const noexcept(noexcept(Field::accessor_type::get(object.unsafe_value()))) { + return Field::accessor_type::get(object.unsafe_value()); + } + value_type& read(object_type& object) const noexcept(noexcept(Field::accessor_type::get(object.unsafe_value()))) { + return Field::accessor_type::get(object.unsafe_value()); + } + template + void write(object_type& object, Value&& value) const requires writable && std::assignable_from { + Field::accessor_type::get(object.unsafe_value()) = std::forward(value); + } +}; +template +auto make_structive_property() { + using Accessor = Managed_Field_Accessor; + using Key = Managed_Key_Attribute; + if constexpr(managed_field_writable_v) { + using Capability = structive::Property_Capability_Attribute; + return structive::Property_Descriptor{{}, {Key{}, Capability{}}}; + } else { + using Capability = structive::Property_Capability_Attribute; + return structive::Property_Descriptor{{}, {Key{}, Capability{}}}; + } +} +template +auto make_managed_schema_impl(std::index_sequence) { + using Fields = Descriptor_Fields; + structive::Synchronization_Plan plan; + ([&] { + using Field = std::tuple_element_t; + if constexpr(managed_field_writable_v && !field_requires_synchronization()) { + plan.unsynchronized(Managed_Key_Attribute::value.view()); + } + }(), ...); + return structive::object(std::move(plan), make_structive_property, Indexes>()...); +} +template +auto make_managed_schema() { + return make_managed_schema_impl(std::make_index_sequence>>{}); +} +template +consteval std::size_t described_member_index() { + using Fields = Descriptor_Fields; + if constexpr(Index == std::tuple_size_v) { + return Index; + } else { + using Field = std::tuple_element_t; + using Accessor = typename Field::accessor_type; + if constexpr(requires { Accessor::member; } && std::same_as, decltype(Member)>) { + if constexpr(Accessor::member == Member) { + return Index; + } + } + return described_member_index(); + } +} +template +consteval bool managed_member_path_writable() { + static_assert(Described_Type>); + constexpr auto index = described_member_index, Member>(); + using Field = Descriptor_Field, index>; + if constexpr(sizeof...(Rest) == 0) { + return Field::managed_writable; + } else { + return managed_member_path_writable(); + } +} +template +consteval bool managed_member_path_requires_synchronization() { + static_assert(Described_Type>); + constexpr auto index = described_member_index, Member>(); + using Field = Descriptor_Field, index>; + if constexpr(Field::managed_writable) { + return Field::synchronized; + } else if constexpr(sizeof...(Rest) == 0) { + return false; + } else { + return managed_member_path_requires_synchronization(); + } +} +template +consteval bool managed_path_writable() { + using Field = Descriptor_Field; + if constexpr(sizeof...(Members) == 0) { + return Field::managed_writable; + } else { + return managed_member_path_writable(); + } +} +template +consteval bool managed_path_requires_synchronization() { + using Field = Descriptor_Field; + if constexpr(Field::managed_writable) { + return Field::synchronized; + } else if constexpr(sizeof...(Members) == 0) { + return false; + } else { + return managed_member_path_requires_synchronization(); + } +} +template +decltype(auto) path_value(Object& object) { + auto&& next = object.*Member; + if constexpr(sizeof...(Rest) == 0) { + return std::forward(next); + } else { + return path_value(next); + } +} +template +decltype(auto) path_value(const Object& object) { + auto&& next = object.*Member; + if constexpr(sizeof...(Rest) == 0) { + return std::forward(next); + } else { + return path_value(next); + } +} +template +decltype(auto) managed_path_value(T& object) { + using Field = Descriptor_Field; + auto&& root = Field::accessor_type::get(object); + if constexpr(sizeof...(Members) == 0) { + return std::forward(root); + } else { + return path_value(root); + } +} +template +decltype(auto) managed_path_value(const T& object) { + using Field = Descriptor_Field; + auto&& root = Field::accessor_type::get(object); + if constexpr(sizeof...(Members) == 0) { + return std::forward(root); + } else { + return path_value(root); + } +} +template +auto managed_key_array_impl(std::index_sequence) { + return std::array{Managed_Key_Attribute::value.view()...}; +} +template +const auto& managed_key_array() { + static const auto value = managed_key_array_impl(std::make_index_sequence>>{}); + return value; +} +} +} +namespace structive { +template +struct Type_Descriptor> { + static auto get() { + return adminive::detail::make_managed_schema, T>(); + } +}; +} +namespace adminive { +template +class Managed_Value : public structive::Property_Object, Policy> { + using Base = structive::Property_Object, Policy>; +public: + using value_type = T; + using policy_type = Policy; + Managed_Value() requires std::default_initializable = default; + explicit Managed_Value(T value) : value_(std::move(value)) {} + explicit Managed_Value(structive::Property_Synchronization synchronization) : Base(std::move(synchronization)) {} + Managed_Value(T value, structive::Property_Synchronization synchronization) : Base(std::move(synchronization)), value_(std::move(value)) {} + Managed_Value(const Managed_Value&) requires std::copy_constructible = default; + Managed_Value(Managed_Value&&) noexcept(std::is_nothrow_move_constructible_v) requires std::move_constructible = default; + Managed_Value& operator=(const Managed_Value&) requires std::is_copy_assignable_v = default; + Managed_Value& operator=(Managed_Value&&) noexcept(std::is_nothrow_move_assignable_v) requires std::is_move_assignable_v = default; + T& unsafe_value() noexcept { + return value_; + } + const T& unsafe_value() const noexcept { + return value_; + } + template + decltype(auto) read(Function&& function) const requires std::invocable && detail::Managed_Callback_Result> { + const auto& keys = detail::managed_key_array(); + auto guard = this->lock_shared(std::span{keys}); + return std::invoke(std::forward(function), std::as_const(value_)); + } + template + decltype(auto) write(Function&& function) requires std::invocable && detail::Managed_Callback_Result> { + using Result = std::invoke_result_t; + if constexpr(std::is_void_v) { + this->with_all_writable_locked([&](auto&) { + std::invoke(std::forward(function), value_); + }); + } else { + std::optional> result; + this->with_all_writable_locked([&](auto&) { + result.emplace(std::invoke(std::forward(function), value_)); + }); + return std::move(*result); + } + } + T snapshot() const requires std::copy_constructible { + return read([](const T& value) { + return value; + }); + } + template + auto member() { + constexpr auto index = detail::described_member_index(); + static_assert(index < std::tuple_size_v>); + return Managed_Field{*this}; + } + template + auto member() const { + constexpr auto index = detail::described_member_index(); + static_assert(index < std::tuple_size_v>); + return Managed_Field{*this}; + } + template + auto field() { + static_assert(Index < std::tuple_size_v>); + return Managed_Field{*this}; + } + template + auto field() const { + static_assert(Index < std::tuple_size_v>); + return Managed_Field{*this}; + } +private: + T value_{}; +}; +template +class Managed_Field { + using Owner = std::remove_const_t; + using Root = typename Owner::value_type; +public: + using owner_type = Managed; + using value_type = std::remove_cvref_t(std::declval()))>; + explicit Managed_Field(Managed& owner) : owner_(&owner) {} + template + decltype(auto) read(Function&& function) const requires std::invocable && detail::Managed_Callback_Result> { + if constexpr(detail::managed_path_requires_synchronization()) { + const auto key = detail::Managed_Key_Attribute::value.view(); + auto guard = owner_->lock_shared({key}); + const auto& value = detail::managed_path_value(owner_->unsafe_value()); + return std::invoke(std::forward(function), value); + } else { + const auto& value = detail::managed_path_value(owner_->unsafe_value()); + return std::invoke(std::forward(function), value); + } + } + template + decltype(auto) write(Function&& function) requires (!std::is_const_v) && (detail::managed_path_writable()) && std::invocable && detail::Managed_Callback_Result> { + if constexpr(detail::managed_path_requires_synchronization()) { + const auto key = detail::Managed_Key_Attribute::value.view(); + auto guard = owner_->lock_unique({key}); + auto& value = detail::managed_path_value(owner_->unsafe_value()); + return std::invoke(std::forward(function), value); + } else { + auto& value = detail::managed_path_value(owner_->unsafe_value()); + return std::invoke(std::forward(function), value); + } + } + value_type snapshot() const requires std::copy_constructible { + return read([](const value_type& value) { + return value; + }); + } + template + auto member() requires (!std::is_const_v) { + return Managed_Field{*owner_}; + } + template + auto member() const { + return Managed_Field{*owner_}; + } + Managed& owner() const noexcept { + return *owner_; + } +private: + Managed* owner_; +}; +template +struct Is_Managed_Value : std::false_type {}; +template +struct Is_Managed_Value> : std::true_type {}; +template +concept Managed_Value_Type = Is_Managed_Value>::value; +template +struct Is_Managed_Field : std::false_type {}; +template +struct Is_Managed_Field> : std::true_type {}; +template +concept Managed_Field_Type = Is_Managed_Field>::value; +} diff --git a/backend/library/tests/core_adapter_test.cpp b/backend/library/tests/core_adapter_test.cpp index 92a1b84..e9bf564 100644 --- a/backend/library/tests/core_adapter_test.cpp +++ b/backend/library/tests/core_adapter_test.cpp @@ -285,7 +285,7 @@ int main() { const Json encoded = adminive::to_json(config); assert((adminive::json_get(adminive::Json_Adapter::at(encoded, "worker_count")) == 12)); assert((adminive::json_get(adminive::Json_Adapter::at(encoded, "mode")) == "secondary")); - adminive::Collection_Service collection("/configs", {Config{}}); + adminive::Collection_Service collection("/configs", {Config{}}); const auto list = collection.list_response(); assert(list.status == 200); const auto& data = adminive::Json_Adapter::at(list.body, "data"); diff --git a/backend/service/CMakeLists.txt b/backend/service/CMakeLists.txt index f193942..7a97d32 100644 --- a/backend/service/CMakeLists.txt +++ b/backend/service/CMakeLists.txt @@ -65,9 +65,9 @@ if(BUILD_TESTING) target_include_directories(Adminive_Config_Store_Test PRIVATE "${CMAKE_CURRENT_LIST_DIR}/src") target_link_libraries(Adminive_Config_Store_Test PRIVATE Adminive_Example) add_test(NAME Adminive_Config_Store_Test COMMAND Adminive_Config_Store_Test) - add_executable(Adminive_Synchronized_Test "${CMAKE_CURRENT_LIST_DIR}/tests/synchronized_test.cpp") - target_link_libraries(Adminive_Synchronized_Test PRIVATE Adminive::Default) - add_test(NAME Adminive_Synchronized_Test COMMAND Adminive_Synchronized_Test) + add_executable(Adminive_Managed_Test "${CMAKE_CURRENT_LIST_DIR}/tests/managed_test.cpp") + target_link_libraries(Adminive_Managed_Test PRIVATE Adminive::Default) + add_test(NAME Adminive_Managed_Test COMMAND Adminive_Managed_Test) add_executable(Adminive_Drogon_Adapter_Test "${CMAKE_CURRENT_LIST_DIR}/tests/drogon_adapter_test.cpp") target_include_directories(Adminive_Drogon_Adapter_Test BEFORE PRIVATE "${CMAKE_CURRENT_LIST_DIR}/tests/fake_drogon") target_link_libraries(Adminive_Drogon_Adapter_Test PRIVATE Adminive::Default) @@ -78,7 +78,7 @@ if(BUILD_TESTING) target_compile_options(Adminive_Drogon_Adapter_Test PRIVATE /permissive-) target_compile_options(Adminive_Safety_Test PRIVATE /permissive-) target_compile_options(Adminive_Config_Store_Test PRIVATE /permissive-) - target_compile_options(Adminive_Synchronized_Test PRIVATE /permissive-) + target_compile_options(Adminive_Managed_Test PRIVATE /permissive-) endif() endif() install(TARGETS Adminive_Service Adminive_Nlohmann Adminive_MagicEnum Adminive_BoostPfr Adminive_Httplib Adminive_Default EXPORT AdminiveServiceTargets) diff --git a/backend/service/include/adminive/adapters/drogon.hpp b/backend/service/include/adminive/adapters/drogon.hpp index 7361fb6..650a555 100644 --- a/backend/service/include/adminive/adapters/drogon.hpp +++ b/backend/service/include/adminive/adapters/drogon.hpp @@ -82,11 +82,11 @@ public: using Service = Resource_Service; using Transaction = typename Service::Transaction; Drogon_Resource(T& value, std::string path, Transaction transaction = {}, Lock* shared_lock = nullptr) : service_(std::make_shared(value, std::move(path), std::move(transaction), shared_lock)) {} - template - explicit Drogon_Resource(Synchronized_Value, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared(value, std::move(path), std::move(transaction))) {} - template - requires (!std::is_const_v) && std::same_as::value_type, std::remove_cvref_t> - explicit Drogon_Resource(Synchronized_Field value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : service_(std::make_shared(value, std::move(path), std::move(transaction), lock_scope)) {} + template + explicit Drogon_Resource(Managed_Value, Policy>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared(value, std::move(path), std::move(transaction))) {} + template + requires (!std::is_const_v) && std::same_as::value_type, std::remove_cvref_t> + explicit Drogon_Resource(Managed_Field value, std::string path, Transaction transaction = {}) : service_(std::make_shared(value, std::move(path), std::move(transaction))) {} Json amis_schema() const { return service_->amis_schema(); } diff --git a/backend/service/include/adminive/adapters/httplib.hpp b/backend/service/include/adminive/adapters/httplib.hpp index 6014491..a02c441 100644 --- a/backend/service/include/adminive/adapters/httplib.hpp +++ b/backend/service/include/adminive/adapters/httplib.hpp @@ -36,11 +36,11 @@ public: using Service = Resource_Service; using Transaction = typename Service::Transaction; Http_Resource(T& value, std::string path, Transaction transaction = {}, Lock* shared_lock = nullptr) : service_(std::make_shared(value, std::move(path), std::move(transaction), shared_lock)) {} - template - explicit Http_Resource(Synchronized_Value, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared(value, std::move(path), std::move(transaction))) {} - template - requires (!std::is_const_v) && std::same_as::value_type, std::remove_cvref_t> - explicit Http_Resource(Synchronized_Field value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : service_(std::make_shared(value, std::move(path), std::move(transaction), lock_scope)) {} + template + explicit Http_Resource(Managed_Value, Policy>& value, std::string path, Transaction transaction = {}) : service_(std::make_shared(value, std::move(path), std::move(transaction))) {} + template + requires (!std::is_const_v) && std::same_as::value_type, std::remove_cvref_t> + explicit Http_Resource(Managed_Field value, std::string path, Transaction transaction = {}) : service_(std::make_shared(value, std::move(path), std::move(transaction))) {} Json amis_schema() const { return service_->amis_schema(); } diff --git a/backend/service/src/config_store.cpp b/backend/service/src/config_store.cpp index a1ab19d..850b040 100644 --- a/backend/service/src/config_store.cpp +++ b/backend/service/src/config_store.cpp @@ -31,8 +31,11 @@ void replace_file(const std::filesystem::path& temporary, const std::filesystem: } #endif } +structive::Property_Synchronization shared_config_synchronization() { + return structive::property_synchronization(structive::Synchronization_Plan{structive::Synchronization_Default::shared}); } -Config_Store::Config_Store(std::filesystem::path path) : path_(std::move(path)) { +} +Config_Store::Config_Store(std::filesystem::path path) : path_(std::move(path)), data_(shared_config_synchronization()) { load_or_create(); } Config_Store::Radio_Service_Access Config_Store::radio_service() noexcept { @@ -79,48 +82,48 @@ Resource_Transaction Config_Store::network_table_transacti }); } void Config_Store::persist_radio_service(const Radio_Service_Config& value) { - data_.write([&](Application_Config& data) { - Application_Config candidate = data; + data_.member<&Application_Config::radio_service>().write([&](Radio_Service_Config& target) { + Application_Config candidate = data_.unsafe_value(); candidate.radio_service = value; persist_candidate(candidate); - data = std::move(candidate); + target = value; }); } void Config_Store::persist_alerts(const Alert_Config& value) { - data_.write([&](Application_Config& data) { - Application_Config candidate = data; + data_.member<&Application_Config::alerts>().write([&](Alert_Config& target) { + Application_Config candidate = data_.unsafe_value(); candidate.alerts = value; persist_candidate(candidate); - data = std::move(candidate); + target = value; }); } void Config_Store::persist_serial_table(const Serial_Table_Config& value) { - data_.write([&](Application_Config& data) { - Application_Config candidate = data; + data_.member<&Application_Config::serial_table>().write([&](Serial_Table_Config& target) { + Application_Config candidate = data_.unsafe_value(); candidate.serial_table = value; persist_candidate(candidate); - data = std::move(candidate); + target = value; }); } void Config_Store::persist_network_table(const Network_Table_Config& value) { - data_.write([&](Application_Config& data) { - Application_Config candidate = data; + data_.member<&Application_Config::network_table>().write([&](Network_Table_Config& target) { + Application_Config candidate = data_.unsafe_value(); candidate.network_table = value; persist_candidate(candidate); - data = std::move(candidate); + target = value; }); } void Config_Store::persist_default_device_type(Device_Table_Type value) { - data_.write([&](Application_Config& data) { - Application_Config candidate = data; + data_.member<&Application_Config::default_device_type>().write([&](Device_Table_Type& target) { + Application_Config candidate = data_.unsafe_value(); candidate.default_device_type = value; persist_candidate(candidate); - data = std::move(candidate); + target = value; }); } void Config_Store::load_or_create() { if(!std::filesystem::exists(path_)) { - persist_candidate(detail::Synchronized_Access::value(data_)); + persist_candidate(data_.unsafe_value()); return; } std::ifstream input(path_); @@ -133,7 +136,7 @@ void Config_Store::load_or_create() { if(!result.success) { throw std::runtime_error("invalid configuration file: " + result.message); } - detail::Synchronized_Access::value(data_) = std::move(candidate); + data_.unsafe_value() = std::move(candidate); } void Config_Store::persist_candidate(const Application_Config& value) { const auto parent = path_.parent_path(); @@ -157,24 +160,16 @@ void Config_Store::persist_candidate(const Application_Config& value) { } replace_file(temporary, path_); } -void Config_Store::persist_radio_service_locked(const Radio_Service_Config& value) { - Application_Config candidate = detail::Synchronized_Access::value(data_); - candidate.radio_service = value; - persist_candidate(candidate); +void Config_Store::persist_radio_service_locked(const Radio_Service_Config&) { + persist_candidate(data_.unsafe_value()); } -void Config_Store::persist_alerts_locked(const Alert_Config& value) { - Application_Config candidate = detail::Synchronized_Access::value(data_); - candidate.alerts = value; - persist_candidate(candidate); +void Config_Store::persist_alerts_locked(const Alert_Config&) { + persist_candidate(data_.unsafe_value()); } -void Config_Store::persist_serial_table_locked(const Serial_Table_Config& value) { - Application_Config candidate = detail::Synchronized_Access::value(data_); - candidate.serial_table = value; - persist_candidate(candidate); +void Config_Store::persist_serial_table_locked(const Serial_Table_Config&) { + persist_candidate(data_.unsafe_value()); } -void Config_Store::persist_network_table_locked(const Network_Table_Config& value) { - Application_Config candidate = detail::Synchronized_Access::value(data_); - candidate.network_table = value; - persist_candidate(candidate); +void Config_Store::persist_network_table_locked(const Network_Table_Config&) { + persist_candidate(data_.unsafe_value()); } } diff --git a/backend/service/src/config_store.hpp b/backend/service/src/config_store.hpp index 5c08ac8..ff48422 100644 --- a/backend/service/src/config_store.hpp +++ b/backend/service/src/config_store.hpp @@ -6,7 +6,7 @@ namespace adminive::example { class Config_Store { public: - using Storage = Synchronized_Value; + using Storage = Managed_Value; using Radio_Service_Access = decltype(std::declval().member<&Application_Config::radio_service>()); using Alert_Access = decltype(std::declval().member<&Application_Config::alerts>()); using Serial_Table_Access = decltype(std::declval().member<&Application_Config::serial_table>()); diff --git a/backend/service/src/device_tables.cpp b/backend/service/src/device_tables.cpp index 4952754..03196f6 100644 --- a/backend/service/src/device_tables.cpp +++ b/backend/service/src/device_tables.cpp @@ -22,7 +22,7 @@ Json make_table_page(std::string_view title, std::string_view color, Json config return Json{{"type", "container"}, {"body", Json::array({std::move(heading), std::move(config_form), std::move(table_page.at("body"))})}}; } } -Serial_Device_Table::Serial_Device_Table(Config_Store& store) : store_(store), config_resource_(store.serial_table(), "/admin/device_tables/serial/config", store.serial_table_transaction(), Resource_Lock_Scope::root), rows_("/admin/device_tables/serial/items", make_serial_rows()) {} +Serial_Device_Table::Serial_Device_Table(Config_Store& store) : store_(store), config_resource_(store.serial_table(), "/admin/device_tables/serial/config", store.serial_table_transaction()), rows_("/admin/device_tables/serial/items", make_serial_rows()) {} Device_Table_Type Serial_Device_Table::type() const noexcept { return Device_Table_Type::serial; } @@ -40,7 +40,7 @@ void Serial_Device_Table::bind(httplib::Server& server) { config_resource_.bind(server); rows_.bind(server); } -Network_Device_Table::Network_Device_Table(Config_Store& store) : store_(store), config_resource_(store.network_table(), "/admin/device_tables/network/config", store.network_table_transaction(), Resource_Lock_Scope::root), rows_("/admin/device_tables/network/items", make_network_rows()) {} +Network_Device_Table::Network_Device_Table(Config_Store& store) : store_(store), config_resource_(store.network_table(), "/admin/device_tables/network/config", store.network_table_transaction()), rows_("/admin/device_tables/network/items", make_network_rows()) {} Device_Table_Type Network_Device_Table::type() const noexcept { return Device_Table_Type::network; } diff --git a/backend/service/src/example_descriptors.hpp b/backend/service/src/example_descriptors.hpp index d445de7..6cde774 100644 --- a/backend/service/src/example_descriptors.hpp +++ b/backend/service/src/example_descriptors.hpp @@ -105,11 +105,11 @@ struct Type_Descriptor { using T = example::Application_Config; return object( "application_config", - ADMINIVE_FIELD_LABEL(T, default_device_type, "默认设备类型").locked(), - ADMINIVE_FIELD_LABEL(T, radio_service, "无线电服务"), - ADMINIVE_FIELD_LABEL(T, alerts, "告警"), - ADMINIVE_FIELD_LABEL(T, serial_table, "串口表格"), - ADMINIVE_FIELD_LABEL(T, network_table, "网络表格") + ADMINIVE_FIELD_LABEL(T, default_device_type, "默认设备类型").read_write(), + ADMINIVE_FIELD_LABEL(T, radio_service, "无线电服务").read_write(), + ADMINIVE_FIELD_LABEL(T, alerts, "告警").read_write(), + ADMINIVE_FIELD_LABEL(T, serial_table, "串口表格").read_write(), + ADMINIVE_FIELD_LABEL(T, network_table, "网络表格").read_write() ).label("Adminive 配置"); } }; diff --git a/backend/service/src/server_app.cpp b/backend/service/src/server_app.cpp index a08f07f..f376bb3 100644 --- a/backend/service/src/server_app.cpp +++ b/backend/service/src/server_app.cpp @@ -12,7 +12,7 @@ Radio_State make_radio_state(Radio_Mode mode, int port, int buffer_count, int lo return result; } } -Server_App::Server_App(int port, std::filesystem::path frontend_dist, std::filesystem::path config_file) : port_(port), frontend_dist_(std::move(frontend_dist)), config_store_(std::move(config_file)), state_resource_("/admin/radio_states", make_radio_states()), service_config_resource_(config_store_.radio_service(), "/admin/config/radio", config_store_.radio_service_transaction(), Resource_Lock_Scope::root), alert_config_resource_(config_store_.alerts(), "/admin/config/alerts", config_store_.alerts_transaction(), Resource_Lock_Scope::root), device_tables_(config_store_) { +Server_App::Server_App(int port, std::filesystem::path frontend_dist, std::filesystem::path config_file) : port_(port), frontend_dist_(std::move(frontend_dist)), config_store_(std::move(config_file)), state_resource_("/admin/radio_states", make_radio_states()), service_config_resource_(config_store_.radio_service(), "/admin/config/radio", config_store_.radio_service_transaction()), alert_config_resource_(config_store_.alerts(), "/admin/config/alerts", config_store_.alerts_transaction()), device_tables_(config_store_) { state_resource_.register_overview_status("/admin/status", 2000); state_resource_.register_status<&Radio_State::status>(2000); bind_routes(); diff --git a/backend/service/tests/advanced_adapter_test.cpp b/backend/service/tests/advanced_adapter_test.cpp index 79ca14c..5634b9e 100644 --- a/backend/service/tests/advanced_adapter_test.cpp +++ b/backend/service/tests/advanced_adapter_test.cpp @@ -289,15 +289,15 @@ int main() { const auto inherited = adminive::to_descriptor_json(); assert(inherited.at("fields").at(0).at("name") == "alpha"); assert(inherited.at("fields").at(1).at("name") == "beta"); - adminive::Synchronized_Value synchronized_inherited; - synchronized_inherited.field<0>().write([](int& value) { + adminive::Managed_Value managed_inherited; + managed_inherited.field<0>().write([](int& value) { value = 12; }); - synchronized_inherited.field<1>().write([](std::string& value) { + managed_inherited.field<1>().write([](std::string& value) { value = "changed"; }); - assert(synchronized_inherited.field<0>().snapshot() == 12); - assert(synchronized_inherited.field<1>().snapshot() == "changed"); + assert(managed_inherited.field<0>().snapshot() == 12); + assert(managed_inherited.field<1>().snapshot() == "changed"); Control_Config controls; const auto control_form = adminive::to_amis_form_schema(controls); assert(control_form.at("body").at(0).at("type") == "input-color"); diff --git a/backend/service/tests/managed_test.cpp b/backend/service/tests/managed_test.cpp new file mode 100644 index 0000000..8890500 --- /dev/null +++ b/backend/service/tests/managed_test.cpp @@ -0,0 +1,190 @@ +#include "adminive/adapters/nlohmann_json.hpp" +#include "adminive/http.hpp" +#include +#include +#include +#include +namespace managed_test { +using Json = nlohmann::json; +struct Section_Config { + int editable_value{1}; + int immutable_value{2}; + int backend_value{3}; + int unsynchronized_value{4}; +}; +struct Root_Config { + Section_Config section; + int immutable_value{5}; +}; +struct Concurrent_Config { + int first{}; + int second{}; + int immutable_value{7}; +}; +struct Probe_Lock { + static inline std::atomic exclusive_locks{}; + static inline std::atomic shared_locks{}; + void lock() noexcept { + exclusive_locks.fetch_add(1, std::memory_order_relaxed); + } + void unlock() noexcept {} + void lock_shared() noexcept { + shared_locks.fetch_add(1, std::memory_order_relaxed); + } + void unlock_shared() noexcept {} + static void reset() noexcept { + exclusive_locks.store(0, std::memory_order_relaxed); + shared_locks.store(0, std::memory_order_relaxed); + } +}; +template +concept Writable_Field = requires(Field field) { + field.write([](auto& value) { + ++value; + }); +}; +template +concept Reference_Escaping_Read = requires(Field field) { + field.read([](const auto& value) -> const auto& { + return value; + }); +}; +} +namespace adminive { +template <> +struct Type_Descriptor { + static auto get() { + using T = managed_test::Section_Config; + return object("section", "Section", ADMINIVE_FIELD(T, editable_value).editable(), ADMINIVE_FIELD(T, immutable_value), ADMINIVE_FIELD(T, backend_value).read_write(), ADMINIVE_FIELD(T, unsynchronized_value).editable().unsynchronized()); + } +}; +template <> +struct Type_Descriptor { + static auto get() { + using T = managed_test::Root_Config; + return object("root", "Root", ADMINIVE_FIELD(T, section), ADMINIVE_FIELD(T, immutable_value)); + } +}; +template <> +struct Type_Descriptor { + static auto get() { + using T = managed_test::Concurrent_Config; + return object("concurrent", "Concurrent", ADMINIVE_FIELD(T, first).editable(), ADMINIVE_FIELD(T, second).editable(), ADMINIVE_FIELD(T, immutable_value)); + } +}; +} +int main() { + using namespace managed_test; + using Guarded = adminive::Managed_Value>; + Guarded guarded; + auto immutable = guarded.member<&Section_Config::immutable_value>(); + auto editable = guarded.member<&Section_Config::editable_value>(); + auto backend = guarded.member<&Section_Config::backend_value>(); + auto unsynchronized = guarded.member<&Section_Config::unsynchronized_value>(); + static_assert(!Writable_Field); + static_assert(Writable_Field); + static_assert(Writable_Field); + static_assert(Writable_Field); + static_assert(!Reference_Escaping_Read); + const auto resolved = guarded.resolved_synchronization(); + assert(resolved.lock_count == 2); + assert(resolved.slot(0) != structive::Resolved_Synchronization_View::unsynchronized_slot); + assert(resolved.slot(1) == structive::Resolved_Synchronization_View::unsynchronized_slot); + assert(resolved.slot(2) != structive::Resolved_Synchronization_View::unsynchronized_slot); + assert(resolved.slot(0) != resolved.slot(2)); + assert(resolved.slot(3) == structive::Resolved_Synchronization_View::unsynchronized_slot); + Probe_Lock::reset(); + assert(immutable.snapshot() == 2); + assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0); + assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); + Probe_Lock::reset(); + editable.write([](int& value) { + value = 11; + }); + assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1); + assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); + Probe_Lock::reset(); + backend.write([](int& value) { + value = 12; + }); + assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1); + Probe_Lock::reset(); + unsynchronized.write([](int& value) { + value = 13; + }); + assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0); + assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); + const auto descriptor = adminive::to_descriptor_json(); + assert(descriptor.at("protocol_version") == 4); + assert(!descriptor.at("fields").at(0).contains("lock_mode")); + assert(descriptor.at("fields").at(0).at("writable") == true); + assert(descriptor.at("fields").at(0).at("synchronized") == true); + assert(descriptor.at("fields").at(1).at("writable") == false); + assert(descriptor.at("fields").at(1).at("synchronized") == false); + assert(descriptor.at("fields").at(2).at("writable") == true); + assert(descriptor.at("fields").at(2).at("editable") == false); + assert(descriptor.at("fields").at(2).at("synchronized") == true); + assert(descriptor.at("fields").at(3).at("writable") == true); + assert(descriptor.at("fields").at(3).at("synchronized") == false); + adminive::Managed_Value> nested; + auto section = nested.member<&Root_Config::section>(); + static_assert(!Writable_Field); + Probe_Lock::reset(); + assert(section.member<&Section_Config::immutable_value>().snapshot() == 2); + assert(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); + Probe_Lock::reset(); + section.member<&Section_Config::editable_value>().write([](int& value) { + value = 31; + }); + assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1); + Probe_Lock::reset(); + section.member<&Section_Config::unsynchronized_value>().write([](int& value) { + value = 32; + }); + assert(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0); + adminive::Managed_Value no_lock; + adminive::Resource_Service service(no_lock, "/section"); + const auto rejected = service.update_response(R"({"backend_value":99,"immutable_value":88})"); + assert(rejected.status == 422); + const auto update = service.update_response(R"({"editable_value":21,"unsynchronized_value":22})"); + assert(update.status == 200); + const auto no_lock_snapshot = no_lock.snapshot(); + assert(no_lock_snapshot.editable_value == 21); + assert(no_lock_snapshot.backend_value == 3); + assert(no_lock_snapshot.immutable_value == 2); + assert(no_lock_snapshot.unsynchronized_value == 22); + adminive::Managed_Value concurrent; + std::atomic start{}; + auto first_writer = std::thread([&] { + while(!start.load(std::memory_order_acquire)) {} + for(int index = 0; index < 5000; ++index) { + concurrent.member<&Concurrent_Config::first>().write([index](int& value) { + value = index; + }); + } + }); + auto second_writer = std::thread([&] { + while(!start.load(std::memory_order_acquire)) {} + for(int index = 0; index < 5000; ++index) { + concurrent.member<&Concurrent_Config::second>().write([index](int& value) { + value = index; + }); + } + }); + auto serializer = std::thread([&] { + while(!start.load(std::memory_order_acquire)) {} + for(int index = 0; index < 1000; ++index) { + const Json value = adminive::to_json(concurrent); + assert(value.at("immutable_value") == 7); + } + }); + start.store(true, std::memory_order_release); + first_writer.join(); + second_writer.join(); + serializer.join(); + const auto concurrent_snapshot = concurrent.snapshot(); + assert(concurrent_snapshot.first == 4999); + assert(concurrent_snapshot.second == 4999); + assert(concurrent_snapshot.immutable_value == 7); + return 0; +} diff --git a/install_consumer/CMakeLists.txt b/install_consumer/CMakeLists.txt new file mode 100644 index 0000000..ded4bef --- /dev/null +++ b/install_consumer/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.20) +project(AdminiveInstallConsumer LANGUAGES CXX) +find_package(Adminive CONFIG REQUIRED COMPONENTS Core) +add_executable(consumer main.cpp) +target_link_libraries(consumer PRIVATE Adminive::Core) +target_compile_features(consumer PRIVATE cxx_std_20)