From 44af33bee63b995a5f12e61d339982fb583ca2da Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Thu, 20 Aug 2026 14:45:12 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=96=E8=AF=91=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 42 +++ Error_handling_specification.md | 130 +++++++++ Project_naming_conventions.md | 58 ++++ kernel/main.cmake | 56 +--- .../kernel/{base => double_buffer}/core.hpp | 263 ++++++++++-------- .../kernel/{base => double_buffer}/object.hpp | 106 ++++--- .../kernel/{base => double_buffer}/rely.hpp | 67 +++-- .../{base => double_buffer}/rely_storage.hpp | 130 +++++---- kernel/src/kernel/render.hpp | 2 +- kernel/src/test/double_buffer_test.cpp | 45 +++ kernel/src/test/main.cpp | 7 +- kernel/src/test/object_test.cpp | 95 +++++++ kernel/src/test/rely_test.cpp | 111 ++++++++ kernel/src/test/render_test.cpp | 172 ++++++++++++ 14 files changed, 991 insertions(+), 293 deletions(-) create mode 100644 AGENTS.md create mode 100644 Error_handling_specification.md create mode 100644 Project_naming_conventions.md rename kernel/src/kernel/{base => double_buffer}/core.hpp (69%) rename kernel/src/kernel/{base => double_buffer}/object.hpp (68%) rename kernel/src/kernel/{base => double_buffer}/rely.hpp (88%) rename kernel/src/kernel/{base => double_buffer}/rely_storage.hpp (67%) create mode 100644 kernel/src/test/double_buffer_test.cpp create mode 100644 kernel/src/test/object_test.cpp create mode 100644 kernel/src/test/rely_test.cpp create mode 100644 kernel/src/test/render_test.cpp diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9d6b0a2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,42 @@ +## 设计约束 + +我使用 里的这个环境 D:\ae\proj\Aethera\CMakePresets.json "toolchain/vs2022.json" +====================[ 构建 | Aethera_Kernel_check | vs2022_debug ]================ +"C:\Program Files\JetBrains\CLion 2026.1\bin\cmake\win\x64\bin\cmake.exe" --build D: +\ae\proj\Aethera\cmake-build-vs2022_debug --target Aethera_Kernel_check -j 30 默认每次运行程序都通过CDB运行 D: +\ae\ewdk\EWDK_22621_230929-1800\Program Files\Windows Kits\10\Debuggers\x64\cdb.exe + +编译器环境脚本 C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\vsdevcmd\ext\VsDevCmd.bat +-host_arch=x64 -arch=x64 + +D:\ae\tools 可能会有有用的工具 + +任何 fallback 都必须先规划,禁止直接实现 + +写函数 和模块使用下面的约定 +[错误处理规范](./Error_handling_specification.md) +[命名约定](./Project_naming_conventions.md) + +0. 跟你审计报告 就要大刀阔斧的改 一次性先把问题一次改完 在批量检查 要大步前进 +1. 每个状态只能有一个权威来源,禁止在不同对象中重复保存并手工同步。 +2. 能通过计算、查询或快照得到的数据,不要保存为成员变量。 +3. 禁止增加只做值转发的成员、getter、setter 和兼容中间层。 +4. 公共接口只表达业务语义,不泄漏缓冲区角色、线程状态、Private 类型和内部指针。 +5. 接口必须正交,避免语义重叠、调用顺序依赖和多套相同实现。 +6. 类只保存自身职责需要的状态,不替其他对象保存配置或运行状态。 +7. 不要用大量默认空实现的虚函数,能力应按需拆分和组合。 +8. 不要提前加入空配置、无消费者统计和未完成接口。 +9. 替换实现时直接删除旧实现,不保留兼容转发。 +10. 修改后检查新增成员的写入者、读取者、生命周期,以及是否形成重复状态源。 + +11. 注意使用 clion提供的工具 Name Url Bearer Token Env Var Status Auth + clion-index http://127.0.0.1:29177/index-mcp/streamable-http - enabled Unsupported + +"C:\Program Files\JetBrains\CLion 2026.1\bin\cmake\win\x64\bin\cmake.exe" -DCMAKE_BUILD_TYPE=Debug +"-DCMAKE_C_COMPILER=C:/Program Files/Microsoft Visual +Studio/18/Enterprise/VC/Tools/MSVC/14.50.35717/bin/Hostx64/x64/cl.bat" "-DCMAKE_CXX_COMPILER=C:/Program Files/Microsoft +Visual Studio/18/Enterprise/VC/Tools/MSVC/14.50.35717/bin/Hostx64/x64/cl.bat" --preset vs2019_Debug -S D:\ae\proj\Radio +-B D:\ae\proj\Radio\cmake-build-vs2019_debug +"C:\Program Files\JetBrains\CLion 2026.1\bin\cmake\win\x64\bin\cmake.exe" --build D: +\ae\proj\Radio\cmake-build-vs2019_debug --target Renderive_Core -j 30 + diff --git a/Error_handling_specification.md b/Error_handling_specification.md new file mode 100644 index 0000000..950edae --- /dev/null +++ b/Error_handling_specification.md @@ -0,0 +1,130 @@ +# 统一错误处理原则 + +## 1. 已知结果处理规则 + +属于 API 契约允许出现、调用方能够明确处理的情况,使用返回码表达。 + +返回码不等于错误码,`timeout`、`cancelled`、`not_ready`、`no_change` 等都可以是正常结果。 + +判断依据只有一个: + +**该结果是否可预料,并且调用方是否具有明确的处理方式。** + +不按“内部/外部”区分。 + +### 返回码定义规则 + +**每个存在返回码的函数必须拥有自己独立的返回码枚举,不得在一个类或模块中定义大而通用的公共返回码枚举供多个函数混用。** + +例如: + +```cpp +enum class Request_Frame_Result { + not_ready, + cancelled +}; + +std::expected request_frame(); +``` + +```cpp +enum class Resize_Result { + no_change, + unsupported +}; + +Resize_Result resize(Size size); +``` + +禁止: + +```cpp +enum class Scene_Result { + not_ready, + cancelled, + no_change, + unsupported, + no_pending_frame, + ... +}; +``` + +然后由多个函数共同返回 `Scene_Result`。 + +规则如下: + +* 有正常返回值,同时存在返回码: + + ```cpp + std::expected + ``` +* 没有额外正常返回值,返回码本身即可完整表达结果: + + ```cpp + Xxx_Result + ``` +* 没有已知返回码: + + ```cpp + T + void + ``` +* 返回码类型必须对应具体函数的契约,函数之间不得为了减少枚举数量而合并返回码。 + +这样可以直接从函数签名确定该函数所有需要调用方处理的已知结果。 + +## 2. 未知失败处理规则 + +不属于正常结果空间,或者当前调用路径没有可靠恢复方式的情况,统一视为 **Unknown Failure**。 + +Unknown Failure 不得转换成 `unknown_error`、`internal_error` 等普通返回码。 + +中间层没有恢复能力时不得层层处理,不增加无意义的 `catch`、包装或 catch/rethrow。 + +Unknown Failure 应直接退出当前执行路径,由上层真正具有故障隔离能力的 request、task、worker、进程或服务边界处理。 + +异步跨线程时可以使用 `std::exception_ptr`、`promise::set_exception()`、`future::get()` 搬运失败;这只是 transport,不属于错误处理。 + +## 3. Unknown Failure Policy + +Unknown Failure 支持两种处置策略: + +```text +fast_fail + -> 在实际故障位置尽快终止 + -> 用于开发、调试和测试 + -> 优先保留故障现场 + +exception + -> 使用异常自然跨层传播 + -> 中间层不处理 + -> 到达上层故障隔离边界 + -> 优先利用 task/thread/process 等隔离能力 +``` + +`fast_fail` 只改变 Unknown Failure 的处置方式, **不得改变 Known Result / Unknown Failure 的分类。** + +## 4. 第三方库处理规则 + +第三方库自身使用返回码还是异常,不决定本系统的处理方式。 + +```text +第三方返回结果 + -> 本系统可预料、可处理 + -> 转换为当前函数自己的返回码 + + -> 本系统没有可靠恢复方式 + -> 转换为 Unknown Failure + -> 交给 Unknown Failure Policy +``` + +不得机械透传第三方错误模型,也不得因为第三方返回错误码,就强制在本系统继续使用错误码。 + +## 5. 其他要求 + +`noexcept` 只用于明确保证异常不会逃逸的函数。 + +不得为了错误处理增加重复检查、兼容层、无意义 `try/catch`,也不得改变原函数的既有语义。 + +**最终原则:每个函数独立定义自己的已知返回结果;Unknown Failure 使用可配置的 Failure +Policy;第三方结果进入系统后重新按同一规则分类。** diff --git a/Project_naming_conventions.md b/Project_naming_conventions.md new file mode 100644 index 0000000..c6c047d --- /dev/null +++ b/Project_naming_conventions.md @@ -0,0 +1,58 @@ +# 项目命名规范 + +* 类型、结构体、枚举、Concept、类型别名:`Upper_Snake_Case` + +```cpp +Scene_Base +Frame_Request_Result +Renderable_Id +``` + +* 函数、参数、局部变量、常量:`lower_snake_case` + +```cpp +render_frame() +frame_count +default_capacity +``` + +* 成员变量:`lower_snake_case_` + +```cpp +scene_ +pending_exception_ +``` + +* 枚举值:`lower_snake_case` + +```cpp +enum class Frame_Request_Result { + none, + cancelled, + renderer_unavailable +}; +``` + +* 模板类型参数:`Upper_Snake_Case` + +```cpp +template +``` + +* 命名空间:`lower_snake_case` + +```cpp +namespace renderive::render_3d +``` + +* 文件名与主要类型一致:`Upper_Snake_Case` + +```text +Scene_Base.hpp +Gpu_Completion_Service.cpp +Low_Latency_Strategy.inl +``` + +* 内部实现统一使用 `detail` 命名空间。 + +**总规则:类型大写下划线,值和函数小写下划线,成员变量末尾加 `_`。** diff --git a/kernel/main.cmake b/kernel/main.cmake index 876d694..9810ddb 100644 --- a/kernel/main.cmake +++ b/kernel/main.cmake @@ -1,5 +1,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/cmake/rely.cmake) set(Aethera_Kernel_dependencies aethera_kernel::taskflow global::magic_enum global::expected) +set(Aethera_BUILD_TESTS TRUE) if (Aethera_BUILD_TESTS) set(Aethera_Kernel_test_targets) list(APPEND Aethera_Kernel_dependencies global::GTest) @@ -22,7 +23,6 @@ endif () set(Aethera_Kernel_source_dir "${CMAKE_CURRENT_LIST_DIR}/src/kernel") append_glob_source(Aethera_Kernel_sources "${Aethera_Kernel_source_dir}") add_library(Aethera_Kernel STATIC ${Aethera_Kernel_sources}) -add_library(Renderive::Kernel ALIAS Aethera_Kernel) set_target_properties(Aethera_Kernel PROPERTIES EXPORT_NAME Kernel) target_include_directories(Aethera_Kernel PUBLIC "$" @@ -35,21 +35,20 @@ target_link_libraries(Aethera_Kernel PUBLIC tl::expected ) target_link_libraries(Aethera_Kernel PRIVATE Threads::Threads) -function(Aethera_stage_kernel_runtime target) - if (WIN32) - add_custom_command(TARGET "${target}" POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "$" - "$" - COMMENT "Staging oneTBB runtime for ${target}" - VERBATIM) - endif () -endfunction() if (MSVC) - target_compile_options(Aethera_Kernel PRIVATE /utf-8) + target_compile_options(Aethera_Kernel PUBLIC /utf-8) +endif () +if (Aethera_BUILD_TESTS) + set(Aethera_Kernel_test_dir "${CMAKE_CURRENT_LIST_DIR}/src/test") + append_glob_source(Aethera_Kernel_test_sources "${Aethera_Kernel_test_dir}") + add_executable(Aethera_Kernel_exe ${Aethera_Kernel_test_sources}) + target_link_libraries(Aethera_Kernel_exe PRIVATE Aethera_Kernel GTest::gtest) + add_test(NAME Aethera_Kernel_exe COMMAND Aethera_Kernel_exe) + set_tests_properties(Aethera_Kernel_exe PROPERTIES + LABELS "Aethera_Kernel" + ENVIRONMENT "Aethera_ERROR_MODE=exception") + list(APPEND Aethera_Kernel_test_targets Aethera_Kernel_exe) endif () -add_executable(Aethera_Kernel_exe "${CMAKE_CURRENT_LIST_DIR}/src/test/main.cpp") -target_link_libraries(Aethera_Kernel_exe PRIVATE Aethera_Kernel) install(TARGETS Aethera_Kernel EXPORT RenderiveTargets ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" @@ -59,35 +58,6 @@ install(DIRECTORY "${Aethera_Kernel_source_dir}/renderive" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.inl") if (Aethera_BUILD_TESTS) - set(Aethera_Kernel_test_dir "${CMAKE_CURRENT_LIST_DIR}/tests") - append_glob_source(Aethera_Kernel_test_sources "${Aethera_Kernel_test_dir}") - # Concrete 2D/3D scene tests belong to their owning modules. The former - # header-only Scene_Context fixtures were removed with the Builder-based - # scene API, so Kernel's suite must remain independently linkable. - list(FILTER Aethera_Kernel_test_sources EXCLUDE REGEX - "(Real_Time_Data_Test|Renderable_Base_Test|Color_Cache_Test|Renderable_Test|Scene_Base_Test|Scene_Test|Dynamic_Renderable_Lifecycle_Test|Render_Plan_Execution_Test|Scene_Inheritance_Test|Scene_Memory_Resource_Test|Scene_State_Observer_Test|Scene2D_Context_Test|Scene2D_Render_Order_Test|Scene3D_Context_Test|Threading_Contract_Test)\\.cpp$") - foreach (Aethera_Kernel_test_source IN LISTS Aethera_Kernel_test_sources) - if (NOT Aethera_Kernel_test_source MATCHES "\\.(c|cc|cpp|cxx)$") - continue() - endif () - file(RELATIVE_PATH Aethera_Kernel_test_name "${Aethera_Kernel_test_dir}" "${Aethera_Kernel_test_source}") - string(REGEX REPLACE "\\.[^.]+$" "" Aethera_Kernel_test_name "${Aethera_Kernel_test_name}") - string(MAKE_C_IDENTIFIER "${Aethera_Kernel_test_name}" Aethera_Kernel_test_name) - set(Aethera_Kernel_test_target "Aethera_Kernel_${Aethera_Kernel_test_name}") - add_executable("${Aethera_Kernel_test_target}" "${Aethera_Kernel_test_source}") - target_include_directories("${Aethera_Kernel_test_target}" PRIVATE - "${Aethera_Kernel_test_dir}" - "${CMAKE_CURRENT_LIST_DIR}/../render_2D" - "${CMAKE_CURRENT_LIST_DIR}/../render_3D" - ) - target_link_libraries("${Aethera_Kernel_test_target}" PRIVATE Aethera_Kernel GTest::gtest_main) - Aethera_stage_kernel_runtime("${Aethera_Kernel_test_target}") - add_test(NAME "${Aethera_Kernel_test_target}" COMMAND "${Aethera_Kernel_test_target}") - set_tests_properties("${Aethera_Kernel_test_target}" PROPERTIES - LABELS "Aethera_Kernel" - ENVIRONMENT "Aethera_ERROR_MODE=exception") - list(APPEND Aethera_Kernel_test_targets "${Aethera_Kernel_test_target}") - endforeach () add_custom_target(Aethera_Kernel_check COMMAND "${CMAKE_CTEST_COMMAND}" --test-dir "${CMAKE_BINARY_DIR}" -C "$" -L "^Aethera_Kernel$" --output-on-failure diff --git a/kernel/src/kernel/base/core.hpp b/kernel/src/kernel/double_buffer/core.hpp similarity index 69% rename from kernel/src/kernel/base/core.hpp rename to kernel/src/kernel/double_buffer/core.hpp index 211ce80..85b63dc 100644 --- a/kernel/src/kernel/base/core.hpp +++ b/kernel/src/kernel/double_buffer/core.hpp @@ -19,16 +19,13 @@ #include #include namespace double_buffer { -template -concept Lockable = requires(T& lock) { +template +concept Lockable = requires(Lock& lock) { { lock.lock() } -> std::same_as; { lock.unlock() } -> std::same_as; }; -template -concept Prop_State = requires { - requires std::default_initializable; - requires std::assignable_from; -}; +template +concept Prop_State = std::default_initializable && std::assignable_from; struct Pmr { using Resource = std::pmr::memory_resource; private: @@ -40,9 +37,9 @@ public: [[nodiscard]] Resource* resource() const noexcept { return value; } - template - std::pmr::polymorphic_allocator allocator() const noexcept { - return std::pmr::polymorphic_allocator{value}; + template + std::pmr::polymorphic_allocator allocator() const noexcept { + return std::pmr::polymorphic_allocator{value}; } static Resource* default_resource() noexcept { return std::pmr::get_default_resource(); @@ -106,14 +103,15 @@ struct Pinned { Pinned(Pinned&&) = delete; Pinned& operator=(Pinned&&) = delete; }; -template +// Double_Buffer 只负责交换 pending/current 两个视图,不复制数据;需要交换后同步基线的场景使用 Synchronized_Double_Buffer。 +template struct Double_Buffer : Pinned { using allocator_type = std::pmr::polymorphic_allocator; private: - std::tuple buf; + std::tuple buf; public: - T* pending; - T* current; + Value* pending; + Value* current; Double_Buffer() : Double_Buffer(std::allocator_arg, allocator_type{std::pmr::get_default_resource()}) {} Double_Buffer(std::allocator_arg_t, const allocator_type& allocator) : buf(std::allocator_arg, allocator), pending(&std::get<0>(buf)), @@ -122,20 +120,47 @@ public: std::swap(pending, current); } }; +enum class Buffer_Direction { + inward, + outward +}; +/* + * 同步双缓冲保留 Double_Buffer 的交换语义,并在交换后把发布值复制回写入侧。 + * inward: pending 是外部写入侧,current 是内部消费侧;outward: current 是内部写入侧,pending 是外部发布侧。 + */ +template +struct Synchronized_Double_Buffer : Double_Buffer { + using Base = Double_Buffer; + using allocator_type = typename Base::allocator_type; + Synchronized_Double_Buffer() = default; + Synchronized_Double_Buffer(std::allocator_arg_t, const allocator_type& allocator) : Base(std::allocator_arg, allocator) {} + void exchange() { + Base::exchange(); + if constexpr (Direction == Buffer_Direction::inward) *this->pending = *this->current; + else *this->current = *this->pending; + } +}; namespace detail { -struct State_Root {}; +struct State_Root { + bool operator==(const State_Root&) const = default; +}; } -template +// State_Type 用 Tag 标记每一层状态,Prev_State 把 CRTP 继承链上的状态按层串起来,供精确回调和类型约束使用。 +template +requires Prop_State struct State_Type : Prev { using Tag_Type = Tag; using Prev_State = Prev; + bool operator==(const State_Type&) const = default; }; -template +template +requires Prop_State struct Tagged_Buffer { using Tag_Type = Tag; - using Value_Type = T; + using Value_Type = Value; }; struct Root; +// Rely_Type 描述一类依赖图及其允许绑定的对象类型;dirty 只表示该依赖阶段需要重新处理,不承担图结构所有权。 template struct Rely_Type { using Tag_Type = Tag; @@ -167,47 +192,45 @@ struct Unique_Types : std::bool_constant< ((!std::same_as) && ...) && Unique_Types::value > {}; -template +template struct Is_Tagged_Buffer : std::false_type {}; -template -struct Is_Tagged_Buffer> : std::true_type {}; -template +template +struct Is_Tagged_Buffer> : std::true_type {}; +template struct Is_Rely_Type : std::false_type {}; template struct Is_Rely_Type> : std::true_type {}; -template +template struct Is_State_Type : std::false_type {}; -template +template struct Is_State_Type> : std::true_type {}; -template -concept Buffer_Type = Is_Tagged_Buffer::value; -template -concept Rely_Mechanism = Is_Rely_Type::value; -template -concept State_Mechanism = Is_State_Type::value; -template -concept Mechanism_Type = requires { - requires Buffer_Type || Rely_Mechanism || State_Mechanism; +template +concept Buffer_Type = Is_Tagged_Buffer::value; +template +concept Rely_Mechanism = Is_Rely_Type::value; +template +concept State_Mechanism = Is_State_Type::value; +template +concept Mechanism_Type = Buffer_Type || Rely_Mechanism || State_Mechanism; +template +concept Tagged_State = Prop_State && requires { + typename Value::Tag_Type; + typename Value::Prev_State; + requires std::derived_from>; }; -template -concept Tagged_State = Prop_State && requires { - typename T::Tag_Type; - typename T::Prev_State; - requires std::derived_from>; -}; -template +template struct State_Layers { using Type = std::tuple<>; }; -template -struct State_Layers { +template +struct State_Layers> { using Type = decltype(std::tuple_cat( - std::declval>(), - std::declval::Type>() + std::declval>(), + std::declval::Type>() )); }; -template -using State_Layers_T = typename State_Layers::Type; +template +using State_Layers_T = typename State_Layers::Type; template struct Member_Pointer_Traits; template @@ -228,11 +251,11 @@ concept State_Member_Settable = requires(State& state, Value&& value) { requires State_Member; state.*Member = std::forward(value); }; -template +template using Mechanism_Buffer_Tuple = std::conditional_t, std::tuple, std::tuple<>>; -template +template using Mechanism_Rely_Tuple = std::conditional_t, std::tuple, std::tuple<>>; -template +template using Mechanism_State_Tuple = std::conditional_t, std::tuple, std::tuple<>>; template struct Has_Tag : std::false_type {}; @@ -274,9 +297,9 @@ concept Rely_Tag_In = requires { requires Rely_List; requires Has_Tag::value; }; -template +template struct Rely_Type_By_Tag; -template +template struct Rely_Type_By_Tag> { private: template @@ -294,9 +317,9 @@ private: public: using Type = typename Find::Type; }; -template requires Rely_Tag_In +template using Rely_Declaration = typename Rely_Type_By_Tag::Type; -template requires Rely_Tag_In +template using Rely_Bound_Object = typename Rely_Declaration::Object_Type; template struct Is_State_List : std::false_type {}; @@ -307,16 +330,16 @@ struct Is_State_List> : Tagged_List_Check< > {}; template concept State_List = Is_State_List::value; -template -concept State_Chain = Tagged_State && State_List>; +template +concept State_Chain = Tagged_State && State_List>; template concept State_Tag_In = requires { requires State_List; requires Has_Tag::value; }; -template -concept State_Chain_Matches = State_Chain && State_List && [] { - using Layers = State_Layers_T; +template +concept State_Chain_Matches = State_Chain && State_List && [] { + using Layers = State_Layers_T; if constexpr (std::tuple_size_v != std::tuple_size_v) return false; else { return [](std::index_sequence) { @@ -327,86 +350,84 @@ concept State_Chain_Matches = State_Chain && State_List && [] { }(std::make_index_sequence>{}); } }(); -template +template struct Rebind_State; -template +template struct Rebind_State, Prev> { using Type = State_Type; }; -template -using Rebind_State_T = typename Rebind_State::Type; -template -consteval std::size_t state_tag_index() { - std::size_t result{}; - std::size_t current{}; - ((std::same_as ? result = current : result, ++current), ...); - return result; +template +using Rebind_State_T = typename Rebind_State::Type; +// Tag 是否存在由外层 requires 保证;内部只按声明顺序计算位置,避免每层重复检查,也避开 MSVC 对复杂 fold 表达式的解析问题。 +template +consteval std::size_t tag_index() { + if constexpr (std::is_same_v) return 0; + else if constexpr (sizeof...(Rest) == 0) return 0; + else return 1 + tag_index(); } -template -struct State_By_Tag; -template -struct State_By_Tag { +template +struct Tag_Index; +template +struct Tag_Index> : std::integral_constant()> {}; +template +inline constexpr std::size_t tag_index_v = Tag_Index::value; +template +struct State_By_Tag { private: - using Layers = State_Layers_T; - template - static consteval std::size_t index(std::index_sequence) { - std::size_t result{}; - ((void)(std::same_as::Tag_Type> ? result = I : result), ...); - return result; - } + using Layers = State_Layers_T; public: - using Type = std::tuple_element_t>{}), Layers>; + using Type = std::tuple_element_t, Layers>; }; -template -using State_Value = typename State_By_Tag::Type; -template -concept State_Callback_For = State_Tag_In && State_Chain_Matches && std::invocable&>; +template +using State_Value = typename State_By_Tag::Type; +template +concept State_Callback_For = State_Tag_In && State_Chain_Matches && std::invocable&>; template -struct State_Callback_Storage_Impl; -template -struct State_Callback_Storage_Impl> { +struct State_Callback_Tuple; +template +struct State_Callback_Tuple> { + using Type = std::tuple...>; +}; +template +struct State_Callback_Storage { private: - std::tuple...> callbacks; + using Layers = State_Layers_T; + using Callbacks = typename State_Callback_Tuple::Type; + Callbacks callbacks; + template + static consteval std::size_t index() { + return tag_index_v; + } public: - template requires - (std::same_as || ...) + template requires State_Tag_In void set(Callback&& callback) { - constexpr auto index = state_tag_index(); - using State = std::tuple_element_t>; - std::get(callbacks) = std::function{std::forward(callback)}; + constexpr auto value_index = index(); + using Layer = std::tuple_element_t; + std::get(callbacks) = std::function{std::forward(callback)}; } - template requires (std::same_as || ...) + template requires State_Tag_In void clear() { - constexpr auto index = state_tag_index(); - std::get(callbacks) = {}; + std::get()>(callbacks) = {}; } - template requires (std::same_as || ...) - void notify(const State& state) { - constexpr auto index = state_tag_index(); - using Layer = std::tuple_element_t>; - auto& callback = std::get(callbacks); + template requires State_Tag_In + void notify(const State_T& state) { + constexpr auto value_index = index(); + using Layer = std::tuple_element_t; + auto& callback = std::get(callbacks); if (callback) callback(static_cast(state)); } }; -template -using State_Callback_Storage = State_Callback_Storage_Impl>; -template -consteval std::size_t tag_index() { - std::size_t result{}; - std::size_t current{}; - ((std::same_as ? result = current : result, ++current), ...); - return result; -} -template +// 每个 State Tag 独立保存回调;字段更新不会隐式通知,发布频率由拥有该状态的内部机制显式调用 notify 控制。 +template struct Buffer_Value_By_Tag; -template +template struct Buffer_Value_By_Tag> { using Type = typename std::tuple_element_t< tag_index(), std::tuple >::Value_Type; }; -template +template using Buffer_Value = typename Buffer_Value_By_Tag::Type; template concept Buffer_Value_Settable = requires(Buffer_Value& target, Value&& value) { @@ -414,25 +435,26 @@ concept Buffer_Value_Settable = requires(Buffer_Value& target, Value requires Buffer_Tag_In; target = std::forward(value); }; -template +// Buffer_Storage 保存普通 Tagged_Buffer 的双缓冲;它只执行基础交换,不做 State/Prop 那种交换后同步。 +template struct Buffer_Storage; -template +template struct Buffer_Storage> { using allocator_type = std::pmr::polymorphic_allocator; private: std::tuple...> buffers; - template > Tag> + template static consteval std::size_t index() { return tag_index(); } public: Buffer_Storage() : Buffer_Storage(std::allocator_arg, allocator_type{std::pmr::get_default_resource()}) {} Buffer_Storage(std::allocator_arg_t, const allocator_type& allocator) : buffers(std::allocator_arg, allocator) {} - template > Tag> + template requires Buffer_Tag_In> auto& get() { return std::get()>(buffers); } - template > Tag> + template requires Buffer_Tag_In> const auto& get() const { return std::get()>(buffers); } @@ -475,9 +497,9 @@ concept Object_Core = requires { requires detail::Rely_List; }; namespace detail { -template +template struct Rely_Storage; -template +template struct Rely_Build_Storage; } enum class Rely_Error { @@ -526,6 +548,7 @@ public: Root() : pmr_resource(), rely_graphs(&pmr_resource), dirty_tags(&pmr_resource) {} + ~Root(); [[nodiscard]] std::pmr::memory_resource* memory_resource() const noexcept { return const_cast(&pmr_resource); } diff --git a/kernel/src/kernel/base/object.hpp b/kernel/src/kernel/double_buffer/object.hpp similarity index 68% rename from kernel/src/kernel/base/object.hpp rename to kernel/src/kernel/double_buffer/object.hpp index 351c3a8..b607eb0 100644 --- a/kernel/src/kernel/base/object.hpp +++ b/kernel/src/kernel/double_buffer/object.hpp @@ -23,26 +23,26 @@ concept Object = requires { requires std::derived_from; }; namespace detail { -template +template using Impl_Buffers = decltype(std::tuple_cat( std::declval(), std::declval>()... )); -template +template using Impl_Relies = decltype(std::tuple_cat( std::declval(), std::declval>()... )); -template +template using Local_States = decltype(std::tuple_cat( std::declval>()... )); -template +template using Impl_States = decltype(std::tuple_cat( std::declval(), std::declval>() )); -template +template using Impl_State_Base = Rebind_State_T< std::tuple_element_t<0, Local_States>, typename Base::State @@ -64,7 +64,8 @@ concept Attached = requires { requires Object_Root; requires std::derived_from; }; -template requires +// Impl 在编译期把 Buffer/Rely/State 三类机制叠加到继承链;每一层只声明自己的机制,最终类型汇总完整能力。 +template requires detail::Impl_Mechanisms struct Impl : Base { using This_Object = Self; @@ -81,18 +82,18 @@ struct Impl : Base { using Relies = detail::Impl_Relies; using States = detail::Impl_States; struct Private : Base_Private { - template - void before_state_set(Self*, Member Owner::*, State*) {} - template - void after_state_set(Self*, Member Owner::*, State*) {} - template - void before_exchange(Self*, Prop*, State*, const Prop*, const State*) {} - template - void after_exchange(Self*, Prop*, State*, const Prop*, const State*) {} + template + void before_state_set(Self*, Member Owner::*, State_T*) {} + template + void after_state_set(Self*, Member Owner::*, State_T*) {} + template + void before_exchange(Self*, Prop_T*, State_T*, const Prop_T*, const State_T*) {} + template + void after_exchange(Self*, Prop_T*, State_T*, const Prop_T*, const State_T*) {} }; using Prev_Private = Private; - template - static void walk_private(Object* object, Callback& callback) { + template + static void walk_private(Object_T* object, Callback& callback) { if constexpr (requires { typename Layer::Prev_Object; }) { if constexpr (!Reverse) walk_private(object, callback); callback(static_cast(object->d)); @@ -100,7 +101,8 @@ struct Impl : Base { } } }; -template +// Attach_Object 是对象运行时唯一数据入口:Prop 向外发布,State 向内提交,普通 Buffer 只交换,Rely 负责依赖图同步。 +template requires Object_Root && Lockable struct Attach_Object : Obj { using Prop = typename Obj::Prop; using State = typename Obj::State; @@ -109,13 +111,13 @@ struct Attach_Object : Obj { using States = typename Obj::States; using Builder = typename Obj::template Builder; using Attached_Object = Obj; - struct Private : Obj::Private, Double_Buffer { + struct Private : Obj::Private, Synchronized_Double_Buffer { using Allocator = std::pmr::polymorphic_allocator; - Double_Buffer state; - detail::State_Callback_Storage state_callbacks; + Synchronized_Double_Buffer state; + detail::State_Callback_Storage state_callbacks; detail::Buffer_Storage buffer_storage; detail::Rely_Storage rely_storage; - explicit Private(std::pmr::memory_resource* resource) : Double_Buffer(std::allocator_arg, Allocator{resource}), + explicit Private(std::pmr::memory_resource* resource) : Synchronized_Double_Buffer(std::allocator_arg, Allocator{resource}), state(std::allocator_arg, Allocator{resource}), buffer_storage(std::allocator_arg, Allocator{resource}), rely_storage(std::allocator_arg, Allocator{resource}) {} @@ -127,7 +129,7 @@ struct Attach_Object : Obj { private: friend struct Root; mutable Lock lock; - template + template void walk_private(Callback& callback) { if constexpr (requires { typename Layer::Prev_Object; }) { if constexpr (!Reverse) walk_private(callback); @@ -175,45 +177,52 @@ private: } void exchange_unlocked() { before_exchange(); - static_cast&>(d).exchange(); + // State 从 pending 向内部 current 提交;Prop 从内部 current 向 pending 发布,两者交换后的写入侧都同步为最新基线。 + static_cast&>(d).exchange(); d.state.exchange(); d.buffer_storage.exchange(); d.rely_storage.exchange(); after_exchange(); } public: - template Tag> + template requires detail::Buffer_Tag_In auto& buffer() { this->emit_rely_source(detail::rely_id>()); return *d.buffer_storage.template get().pending; } - template Tag> + template requires detail::Buffer_Tag_In const auto& current_buffer() const { return *d.buffer_storage.template get().current; } - template Tag, detail::Buffer_Value_Settable Value> + template requires + detail::Buffer_Tag_In && detail::Buffer_Value_Settable void set_initial_buffer(Value&& value) { auto& buffer = d.buffer_storage.template get(); *buffer.pending = std::forward(value); *buffer.current = *buffer.pending; } - template Tag> + template requires detail::Rely_Tag_In [[nodiscard]] auto rely() const { - using Object = detail::Rely_Bound_Object; - return d.rely_storage.template get().pending->template typed_view(); + using Bound_Object = detail::Rely_Bound_Object; + const Rely* graph = d.rely_storage.template get().pending; + return graph->typed_view(); } - template Tag> + template requires detail::Rely_Tag_In [[nodiscard]] auto current_rely() const { - using Object = detail::Rely_Bound_Object; - return d.rely_storage.template get().current->template typed_view(); + using Bound_Object = detail::Rely_Bound_Object; + const Rely* graph = d.rely_storage.template get().current; + return graph->typed_view(); } - template ... Tags, typename Callback> requires - detail::Unique_Types::value && detail::Rely_Access_Callback_For + template requires + (detail::Rely_Tag_In && ...) && detail::Unique_Types::value && + detail::Rely_Access_Callback_For void access_rely(Callback&& callback) { d.rely_storage.template access(std::forward(callback)); } - template ... Tags, typename Callback> requires - detail::Unique_Types::value && detail::Rely_Edit_Callback_For + // 依赖图只允许在编辑侧修改;回调完成后先验证 DAG,再把新结构提交到 pending。 + template requires + (detail::Rely_Tag_In && ...) && detail::Unique_Types::value && + detail::Rely_Edit_Callback_For std::expected edit_rely(Callback&& callback) { std::lock_guard guard(lock); return d.rely_storage.template edit(std::forward(callback)); @@ -221,42 +230,47 @@ public: std::expected validate_rely() const { return d.rely_storage.validate_pending(); } - template Tag, typename Callback> requires + template requires + detail::Rely_Tag_In && detail::Rely_View_Node_Callback> std::expected for_each_rely_topological(Callback&& callback) const { return current_rely().for_each_topological_view(std::forward(callback)); } - template + template requires detail::Rely_Current_Callback void for_each_current_rely(Callback&& callback) const { d.rely_storage.for_each_current(std::forward(callback)); } - template Value> + template requires + detail::Prop_Member_Settable Attach_Object& set(Member Owner::* member, Value&& value) { std::lock_guard guard(lock); - d.pending->*member = std::forward(value); + d.current->*member = std::forward(value); return *this; } - template Callback> + template requires + detail::State_Callback_For void set_state_callback(Callback&& callback) { std::lock_guard guard(lock); d.state_callbacks.template set(std::forward(callback)); } - template Tag> + template requires detail::State_Tag_In void clear_state_callback() { std::lock_guard guard(lock); d.state_callbacks.template clear(); } - template Callback> + template requires + detail::State_Callback_For void access_state(Callback&& callback) const { std::lock_guard guard(lock); using Layer = detail::State_Value; std::invoke(std::forward(callback), static_cast(*d.state.current)); } - template Tag> + template requires detail::State_Tag_In void notify_state() { d.state_callbacks.template notify(*d.state.current); } - template Value> + // State 写入 pending,随后向依赖图发出对应成员的 dirty 信号;真正进入 current 发生在 exchange/process 边界。 + template requires detail::State_Member_Settable void update_state(Value&& value) { std::lock_guard guard(lock); before_state_set(Member); @@ -276,7 +290,7 @@ public: std::lock_guard guard(lock); exchange_unlocked(); } - template Callback> + template requires std::invocable void exchange(Callback&& callback) { std::lock_guard guard(lock); exchange_unlocked(); diff --git a/kernel/src/kernel/base/rely.hpp b/kernel/src/kernel/double_buffer/rely.hpp similarity index 88% rename from kernel/src/kernel/base/rely.hpp rename to kernel/src/kernel/double_buffer/rely.hpp index d083e34..d9ddff1 100644 --- a/kernel/src/kernel/base/rely.hpp +++ b/kernel/src/kernel/double_buffer/rely.hpp @@ -1,6 +1,7 @@ #pragma once #include "core.hpp" namespace double_buffer { +// Rely 是有向无环依赖图。Node 保存对象和直接前驱,Edge 额外记录触发 dirty 的来源键,用于状态/Buffer/阶段脏标记传播。 struct Rely { using Error = Rely_Error; struct Node { @@ -69,12 +70,12 @@ public: std::vector find_edges(Root* target, Root* source) const { return rely->find_edges(target, source); } - template Callback> + template requires std::invocable void for_each_edge(Callback&& callback) const { rely->for_each_edge(std::forward(callback)); } }; - template + template requires Root_Derived class Typed_View : public View { public: using Object_Type = Object; @@ -94,7 +95,7 @@ public: auto* node = this->find(root); return node ? private_data(*node) : nullptr; } - template Callback> + template requires std::invocable void for_each_bound(Callback&& callback) const { this->rely->for_each( [&](const Node& node) { @@ -103,7 +104,7 @@ public: } ); } - template Callback> + template requires std::invocable std::expected for_each_topological_view(Callback&& callback) const { return this->rely->for_each_topological( [&](const Node& node) { @@ -111,12 +112,13 @@ public: } ); } - template Callback> + template requires std::invocable void for_each(Callback&& callback) const { this->rely->for_each(std::forward(callback)); } }; - template + // Editor 只操作待提交图;结构是否合法由编辑完成后的拓扑验证统一判断,不在每个底层操作重复检查。 + template requires Root_Derived class Editor : public View { private: Rely* edit_rely; @@ -134,18 +136,18 @@ public: void clear() { edit_rely->reset(); } - template requires std::derived_from + template requires detail::Bound_Rely_Object && std::derived_from Node* add(Object* object) { return edit_rely->template add_node(object, bind_node_data); } - template requires std::derived_from + template requires detail::Bound_Rely_Object && std::derived_from void add(std::initializer_list objects) { for (auto* object : objects) edit_rely->template add_node(object, bind_node_data); } bool remove(Root* object) { return edit_rely->remove_node(object); } - template requires std::derived_from + template requires Root_Derived && std::derived_from void clear_dependencies(Target* target) { edit_rely->clear_dependencies_impl(target); } @@ -155,7 +157,8 @@ public: void disconnect(Root* object) { edit_rely->disconnect_impl(object); } - template Source> requires + template requires + detail::Bound_Rely_Object && detail::State_Rely_Source && std::derived_from Node* add_dependency(Target* target, Source* source) { return edit_rely->template add_dependency_runtime( @@ -167,7 +170,8 @@ public: bind_node_data ); } - template Source> requires + template requires + detail::Bound_Rely_Object && detail::Buffer_Rely_Source && std::derived_from Node* add_dependency(Target* target, Source* source) { return edit_rely->template add_dependency_runtime( @@ -179,7 +183,8 @@ public: bind_node_data ); } - template requires + template requires + detail::Bound_Rely_Object && detail::Rely_Object && std::derived_from Node* add_dirty_dependency(Target* target, Source* source) { return edit_rely->template add_dependency_runtime( @@ -191,25 +196,28 @@ public: bind_node_data ); } - template Source> requires + template requires + detail::Bound_Rely_Object && detail::State_Rely_Source && std::derived_from bool remove_dependency(Target* target, Source* source) { return edit_rely->remove_edge(target, source, detail::rely_id>(), target_tag); } - template Source> requires + template requires + detail::Bound_Rely_Object && detail::Buffer_Rely_Source && std::derived_from bool remove_dependency(Target* target, Source* source) { return edit_rely->remove_edge(target, source, detail::rely_id>(), target_tag); } - template requires std::derived_from + template requires Root_Derived && std::derived_from bool remove_dependency(Target* target, Root* source) { return edit_rely->remove_dependency_impl(target, source); } }; private: - template + friend struct Root; + template friend struct detail::Rely_Storage; - template + template friend struct detail::Rely_Build_Storage; std::pmr::memory_resource* pmr_resource; std::pmr::list list; @@ -218,6 +226,7 @@ private: std::pmr::unordered_multimap dirty_edges; std::uint64_t structure_revision{}; mutable bool bound{}; + Rely* mirror{}; Node* emplace_node(Root* object, Node::Bind bind) { auto current = nodes.find(object); if (current != nodes.end()) { @@ -325,6 +334,14 @@ private: nodes.clear(); list.clear(); } + void pair_with(Rely& other) noexcept { + mirror = &other; + other.mirror = this; + } + void detach_destroyed(Root* object) { + remove_node(object); + if (mirror) mirror->remove_node(object); + } void reset() { if (list.empty() && edges.empty()) return; clear_data(); @@ -545,10 +562,11 @@ public: } return result; } - template Callback> + template requires std::invocable void for_each_edge(Callback&& callback) const { for (const auto& edge : edges) std::invoke(callback, edge); } + // 拓扑结果既用于验证无环,也定义上层按照依赖顺序构图和遍历的稳定入口。 std::expected, Error> topological_order() const { enum class Visit { none, @@ -583,18 +601,18 @@ public: } return result; } - template Callback> + template requires std::invocable void for_each(Callback&& callback) const { for (const auto& node : list) std::invoke(callback, node); } - template Callback> + template requires std::invocable std::expected for_each_topological(Callback&& callback) const { auto result = topological_order(); if (!result) return std::unexpected(result.error()); for (const auto* node : *result) std::invoke(callback, *node); return {}; } - template Callback> + template requires std::invocable std::expected for_each_topological_view(Callback&& callback) const { View view(*this); return for_each_topological( @@ -615,4 +633,11 @@ public: } } }; +inline Root::~Root() { + while (!rely_graphs.empty()) { + auto* rely = rely_graphs.back(); + rely_graphs.pop_back(); + const_cast(rely)->detach_destroyed(this); + } +} } diff --git a/kernel/src/kernel/base/rely_storage.hpp b/kernel/src/kernel/double_buffer/rely_storage.hpp similarity index 67% rename from kernel/src/kernel/base/rely_storage.hpp rename to kernel/src/kernel/double_buffer/rely_storage.hpp index 76b377e..b4f56fe 100644 --- a/kernel/src/kernel/base/rely_storage.hpp +++ b/kernel/src/kernel/double_buffer/rely_storage.hpp @@ -24,15 +24,16 @@ inline void Root::emit_rely(const void* key) { for (const auto* rely : rely_graphs) rely->emit(this, key); } namespace detail { -template +template struct Rely_Storage; -template +// Rely_Storage 为每个 Rely_Type 保存 pending/current 两份图;编辑只落到 pending,exchange 后 current 成为执行侧并同步新的编辑基线。 +template struct Rely_Storage> { using allocator_type = std::pmr::polymorphic_allocator; private: - template - struct Rely_Buffer : Double_Buffer, Mechanism { - using Base = Double_Buffer; + template + struct Rely_Buffer : Synchronized_Double_Buffer, Mechanism { + using Base = Synchronized_Double_Buffer; using allocator_type = typename Base::allocator_type; Rely_Buffer() = default; Rely_Buffer(std::allocator_arg_t, const allocator_type& allocator) : Base(std::allocator_arg, allocator) {} @@ -41,7 +42,15 @@ private: } }; std::tuple...> relies; - template > Tag> + void pair_buffers() { + std::apply( + [](auto&... buffer) { + (buffer.pending->pair_with(*buffer.current), ...); + }, + relies + ); + } + template static consteval std::size_t index() { return tag_index(); } @@ -56,35 +65,40 @@ private: return validate_pending_impl(); } } - template + template static void exchange_one(Rely_Buffer& buffer) { - Rely mirror(*buffer.pending); + // pending/current revision 相同表示上次交换后没有结构编辑,无需再次深拷贝整张依赖图。 + if (buffer.pending->structure_revision == buffer.current->structure_revision) return; buffer.current->unbind(); buffer.exchange(); buffer.current->bind(); - buffer.pending->swap(mirror); } public: Rely_Storage() : Rely_Storage(std::allocator_arg, allocator_type{std::pmr::get_default_resource()}) {} - Rely_Storage(std::allocator_arg_t, const allocator_type& allocator) : relies(std::allocator_arg, allocator) {} - template > Tag> + Rely_Storage(std::allocator_arg_t, const allocator_type& allocator) : relies(std::allocator_arg, allocator) { + pair_buffers(); + } + template requires Rely_Tag_In> auto& get() { return std::get()>(relies); } - template > Tag> + template requires Rely_Tag_In> const auto& get() const { return std::get()>(relies); } - template >... Tags, typename Callback> requires - Unique_Types::value && Rely_Access_Callback_For, Tags...> + template requires + (Rely_Tag_In> && ...) && Unique_Types::value && + Rely_Access_Callback_For, Tags...> void access(Callback&& callback) { std::invoke( std::forward(callback), static_cast>&>(get())... ); } - template >... Tags, typename Callback> requires - Unique_Types::value && Rely_Edit_Callback_For, Tags...> + // 编辑采用临时图事务:先复制 pending,回调修改临时图并统一验证,成功后才替换 pending,失败不会污染现有结构。 + template requires + (Rely_Tag_In> && ...) && Unique_Types::value && + Rely_Edit_Callback_For, Tags...> std::expected edit(Callback&& callback) { std::tuple...> next(*get().pending...); auto editors = [&](std::index_sequence) { @@ -156,12 +170,13 @@ public: ); } }; -template +// Builder 使用独立依赖图完成对象构造期编辑;build 成功时一次性提交到对象的正式 Rely_Storage。 +template struct Rely_Build_Storage> { using allocator_type = std::pmr::polymorphic_allocator; private: std::tuple...> relies; - template > Tag> + template static consteval std::size_t index() { return tag_index(); } @@ -176,7 +191,7 @@ private: return validate_impl(); } } - template + template void commit_one(Rely_Storage>& storage) { using Tag = typename Rely_Type::Tag_Type; auto& buffer = storage.template get(); @@ -189,16 +204,17 @@ private: public: Rely_Build_Storage() : Rely_Build_Storage(std::pmr::get_default_resource()) {} explicit Rely_Build_Storage(std::pmr::memory_resource* resource) : relies(std::allocator_arg, allocator_type{resource}) {} - template > Tag> + template requires Rely_Tag_In> Rely& get() { return std::get()>(relies); } - template > Tag> + template requires Rely_Tag_In> const Rely& get() const { return std::get()>(relies); } - template >... Tags, typename Callback> requires - Unique_Types::value && Rely_Edit_Callback_For, Tags...> + template requires + (Rely_Tag_In> && ...) && Unique_Types::value && + Rely_Edit_Callback_For, Tags...> void edit(Callback&& callback) { auto editors = std::tuple{ Rely::Editor>>( @@ -241,25 +257,30 @@ struct Root::Builder { Builder& set_pmr(Pmr::Resource* resource) { return set_pmr(Pmr{resource}); } - template Value> + template requires + detail::Prop_Member_Settable Builder& set(Member Owner::* member, Value&& value) { object->set(member, std::forward(value)); return *this; } - template Tag, detail::Buffer_Value_Settable Value> + template requires + detail::Buffer_Tag_In && + detail::Buffer_Value_Settable Builder& set(Value&& value) { object->template set_initial_buffer(std::forward(value)); return *this; } - template ... Rely_Tags, typename Callback> requires + template requires + (detail::Rely_Tag_In && ...) && detail::Unique_Types::value && detail::Rely_Edit_Callback_For Builder& edit_rely(Callback&& callback) { rely_storage.template edit(std::forward(callback)); return *this; } - template Rely_Tag, detail::Bound_Rely_Object Node_Object> requires - (std::derived_from>) + template requires + detail::Rely_Tag_In && detail::Bound_Rely_Object && + std::derived_from> Builder& add_dependency_node(Node_Object* node) { return edit_rely( [&](auto& editor) { @@ -267,7 +288,8 @@ struct Root::Builder { } ); } - template Rely_Tag, Root_Derived Node_Object> + template requires + detail::Rely_Tag_In && Root_Derived Builder& remove_dependency_node(Node_Object* node) { return edit_rely( [&](auto& editor) { @@ -275,12 +297,10 @@ struct Root::Builder { } ); } - template < - detail::Rely_Tag_In Rely_Tag, - auto Member, - detail::Rely_Object Target, - detail::State_Rely_Source Source> requires - (detail::Bound_Rely_Object && std::derived_from>) + template requires + detail::Rely_Tag_In && detail::Rely_Object && + detail::State_Rely_Source && detail::Bound_Rely_Object && + std::derived_from> Builder& add_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -288,12 +308,10 @@ struct Root::Builder { } ); } - template < - detail::Rely_Tag_In Rely_Tag, - typename Buffer_Tag, - detail::Rely_Object Target, - detail::Buffer_Rely_Source Source> requires - (detail::Bound_Rely_Object && std::derived_from>) + template requires + detail::Rely_Tag_In && detail::Rely_Object && + detail::Buffer_Rely_Source && detail::Bound_Rely_Object && + std::derived_from> Builder& add_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -301,12 +319,10 @@ struct Root::Builder { } ); } - template < - detail::Rely_Tag_In Rely_Tag, - typename Source_Tag, - Root_Derived Target, - Root_Derived Source> requires - (detail::Bound_Rely_Object && std::derived_from>) + template requires + detail::Rely_Tag_In && Root_Derived && Root_Derived && + detail::Bound_Rely_Object && + std::derived_from> Builder& add_dirty_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -314,12 +330,10 @@ struct Root::Builder { } ); } - template < - detail::Rely_Tag_In Rely_Tag, - auto Member, - detail::Rely_Object Target, - detail::State_Rely_Source Source> requires - (detail::Bound_Rely_Object && std::derived_from>) + template requires + detail::Rely_Tag_In && detail::Rely_Object && + detail::State_Rely_Source && detail::Bound_Rely_Object && + std::derived_from> Builder& remove_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { @@ -327,12 +341,10 @@ struct Root::Builder { } ); } - template < - detail::Rely_Tag_In Rely_Tag, - typename Buffer_Tag, - detail::Rely_Object Target, - detail::Buffer_Rely_Source Source> requires - (detail::Bound_Rely_Object && std::derived_from>) + template requires + detail::Rely_Tag_In && detail::Rely_Object && + detail::Buffer_Rely_Source && detail::Bound_Rely_Object && + std::derived_from> Builder& remove_dependency(Target* target, Source* source) { return edit_rely( [&](auto& editor) { diff --git a/kernel/src/kernel/render.hpp b/kernel/src/kernel/render.hpp index 4cf02a8..3501255 100644 --- a/kernel/src/kernel/render.hpp +++ b/kernel/src/kernel/render.hpp @@ -1,5 +1,5 @@ #pragma once -#include "base/object.hpp" +#include "double_buffer/object.hpp" #include #include #include diff --git a/kernel/src/test/double_buffer_test.cpp b/kernel/src/test/double_buffer_test.cpp new file mode 100644 index 0000000..186885d --- /dev/null +++ b/kernel/src/test/double_buffer_test.cpp @@ -0,0 +1,45 @@ +#include "double_buffer/core.hpp" +#include +namespace { +struct Value { + int first{}; + int second{}; + bool operator==(const Value&) const = default; +}; +} +TEST(double_buffer, exchange_only_swaps_sides) { + double_buffer::Double_Buffer buffer; + buffer.pending->first = 1; + buffer.current->first = 2; + auto* pending = buffer.pending; + auto* current = buffer.current; + buffer.exchange(); + EXPECT_EQ(buffer.pending, current); + EXPECT_EQ(buffer.current, pending); + EXPECT_EQ(buffer.pending->first, 2); + EXPECT_EQ(buffer.current->first, 1); +} +TEST(synchronized_double_buffer, inward_preserves_incremental_pending_state) { + double_buffer::Synchronized_Double_Buffer buffer; + buffer.pending->first = 7; + buffer.exchange(); + EXPECT_EQ(buffer.current->first, 7); + EXPECT_EQ(buffer.pending->first, 7); + buffer.pending->second = 9; + buffer.exchange(); + EXPECT_EQ(buffer.current->first, 7); + EXPECT_EQ(buffer.current->second, 9); + EXPECT_EQ(*buffer.pending, *buffer.current); +} +TEST(synchronized_double_buffer, outward_preserves_incremental_current_prop) { + double_buffer::Synchronized_Double_Buffer buffer; + buffer.current->first = 11; + buffer.exchange(); + EXPECT_EQ(buffer.pending->first, 11); + EXPECT_EQ(buffer.current->first, 11); + buffer.current->second = 13; + buffer.exchange(); + EXPECT_EQ(buffer.pending->first, 11); + EXPECT_EQ(buffer.pending->second, 13); + EXPECT_EQ(*buffer.pending, *buffer.current); +} diff --git a/kernel/src/test/main.cpp b/kernel/src/test/main.cpp index b315d7f..51aa7e6 100644 --- a/kernel/src/test/main.cpp +++ b/kernel/src/test/main.cpp @@ -1,4 +1,5 @@ -#include "../kernel/render.hpp" -int main() { - return 0; +#include +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/kernel/src/test/object_test.cpp b/kernel/src/test/object_test.cpp new file mode 100644 index 0000000..456e0f4 --- /dev/null +++ b/kernel/src/test/object_test.cpp @@ -0,0 +1,95 @@ +#include "double_buffer/object.hpp" +#include +namespace { +struct Object_State_Tag {}; +struct Test_Object : double_buffer::Impl> { + struct Prop : Prev_Prop { + int first{}; + int second{}; + }; + struct State : Prev_State { + int first{}; + int second{}; + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private {}; +}; +using Object = double_buffer::Attach_Object; +struct Derived_State_Tag {}; +struct Derived_Object : double_buffer::Impl> { + struct Prop : Prev_Prop {}; + struct State : Prev_State { + int derived{}; + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private {}; +}; +using Derived = double_buffer::Attach_Object; +} +TEST(object_buffer, state_moves_inward_and_keeps_incremental_baseline) { + Object object; + object.update_state<&Test_Object::State::first>(3); + object.exchange(); + EXPECT_EQ(object.d.state.current->first, 3); + EXPECT_EQ(object.d.state.pending->first, 3); + object.update_state<&Test_Object::State::second>(5); + object.exchange(); + EXPECT_EQ(object.d.state.current->first, 3); + EXPECT_EQ(object.d.state.current->second, 5); +} +TEST(object_buffer, prop_moves_outward_and_keeps_incremental_baseline) { + Object object; + object.set(&Test_Object::Prop::first, 17); + EXPECT_EQ(object.d.current->first, 17); + EXPECT_EQ(object.d.pending->first, 0); + object.exchange(); + EXPECT_EQ(object.d.pending->first, 17); + EXPECT_EQ(object.d.current->first, 17); + object.set(&Test_Object::Prop::second, 19); + object.exchange(); + EXPECT_EQ(object.d.pending->first, 17); + EXPECT_EQ(object.d.pending->second, 19); +} +TEST(state_tag, callback_publishes_only_requested_layer) { + Object object; + int calls = 0; + object.set_state_callback( + [&](const auto& state) { + ++calls; + EXPECT_EQ(state.first, 23); + } + ); + object.update_state<&Test_Object::State::first>(23); + object.exchange(); + EXPECT_EQ(calls, 0); + object.notify_state(); + EXPECT_EQ(calls, 1); +} +static_assert(requires(Object& object) { + object.template access_state([](const auto&) {}); +}); +struct Missing_State_Tag {}; +static_assert(!double_buffer::detail::State_Tag_In); +TEST(state_tag, inherited_tags_remain_independently_addressable) { + Derived object; + int base_calls = 0; + int derived_calls = 0; + object.set_state_callback([&](const auto&) { ++base_calls; }); + object.set_state_callback([&](const auto&) { ++derived_calls; }); + object.notify_state(); + EXPECT_EQ(base_calls, 1); + EXPECT_EQ(derived_calls, 0); + object.notify_state(); + EXPECT_EQ(base_calls, 1); + EXPECT_EQ(derived_calls, 1); +} +static_assert(double_buffer::detail::State_Tag_In); +static_assert(double_buffer::detail::State_Tag_In); +static_assert(double_buffer::detail::State_Tag_In); +TEST(state_tag, state_chain_keeps_default_equality_usable) { + Test_Object::State first; + Test_Object::State second; + EXPECT_TRUE(first == second); + second.first = 1; + EXPECT_FALSE(first == second); +} diff --git a/kernel/src/test/rely_test.cpp b/kernel/src/test/rely_test.cpp new file mode 100644 index 0000000..2fdbc5d --- /dev/null +++ b/kernel/src/test/rely_test.cpp @@ -0,0 +1,111 @@ +#include "double_buffer/object.hpp" +#include +namespace { +struct Node_State_Tag {}; +struct Graph_State_Tag {}; +struct Graph_Tag {}; +struct Node_Object : double_buffer::Impl> { + struct Prop : Prev_Prop {}; + struct State : Prev_State { + int value{}; + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private {}; +}; +using Node = double_buffer::Attach_Object; +struct Graph_Object : double_buffer::Impl, double_buffer::Rely_Type> { + struct Prop : Prev_Prop {}; + struct State : Prev_State { + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private {}; +}; +using Graph = double_buffer::Attach_Object; +} +TEST(rely_storage, edited_graph_moves_inward_and_stays_synchronized) { + Node first; + Node second; + Graph graph; + auto result = graph.edit_rely( + [&](auto& editor) { + editor.add(&first); + editor.add(&second); + editor.template add_dependency<&Node_Object::State::value>(&second, &first); + } + ); + ASSERT_TRUE(result.has_value()); + auto& buffer = graph.d.rely_storage.get(); + auto* pending_before = buffer.pending; + auto* current_before = buffer.current; + graph.exchange(); + EXPECT_EQ(buffer.current, pending_before); + EXPECT_EQ(buffer.pending, current_before); + EXPECT_EQ(buffer.current->size(), 2u); + EXPECT_EQ(buffer.pending->size(), 2u); + EXPECT_TRUE(buffer.current->depends_on(&second, &first)); + EXPECT_TRUE(buffer.pending->depends_on(&second, &first)); +} +TEST(rely_storage, unchanged_graph_does_not_exchange_again) { + Node node; + Graph graph; + ASSERT_TRUE(graph.edit_rely([&](auto& editor) { editor.add(&node); }).has_value()); + graph.exchange(); + auto& buffer = graph.d.rely_storage.get(); + auto* pending = buffer.pending; + auto* current = buffer.current; + graph.exchange(); + EXPECT_EQ(buffer.pending, pending); + EXPECT_EQ(buffer.current, current); +} +TEST(rely, topological_order_and_state_dirty_propagation) { + Node source; + Node target; + Graph graph; + ASSERT_TRUE(graph.edit_rely( + [&](auto& editor) { + editor.add(&source); + editor.add(&target); + editor.template add_dependency<&Node_Object::State::value>(&target, &source); + } + ).has_value()); + graph.exchange(); + auto view = graph.current_rely(); + std::vector order; + auto result = view.for_each_topological_view( + [&](const auto&, const auto& node) { + order.push_back(node.object); + } + ); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(order.size(), 2u); + EXPECT_EQ(order[0], &source); + EXPECT_EQ(order[1], &target); + source.update_state<&Node_Object::State::value>(31); + EXPECT_TRUE(target.dirty()); +} +TEST(rely, cycle_is_rejected_before_pending_graph_commit) { + Node first; + Node second; + Graph graph; + auto result = graph.edit_rely( + [&](auto& editor) { + editor.add(&first); + editor.add(&second); + editor.template add_dependency<&Node_Object::State::value>(&first, &second); + editor.template add_dependency<&Node_Object::State::value>(&second, &first); + } + ); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), double_buffer::Rely_Error::cycle); + EXPECT_TRUE(graph.rely().empty()); +} +TEST(rely_lifetime, referenced_object_can_be_destroyed_before_graph_owner) { + Graph graph; + auto node = std::make_unique(); + ASSERT_TRUE(graph.edit_rely([&](auto& editor) { editor.add(node.get()); }).has_value()); + graph.exchange(); + ASSERT_EQ(graph.current_rely().size(), 1u); + node.reset(); + EXPECT_TRUE(graph.current_rely().empty()); + EXPECT_TRUE(graph.rely().empty()); +} diff --git a/kernel/src/test/render_test.cpp b/kernel/src/test/render_test.cpp new file mode 100644 index 0000000..9a09703 --- /dev/null +++ b/kernel/src/test/render_test.cpp @@ -0,0 +1,172 @@ +#include "render.hpp" +#include +namespace { +struct Direct_State_Tag {}; +struct Graph_State_Tag {}; +struct Direct_Renderable : double_buffer::Impl> { + struct Prop : Prev_Prop {}; + struct State : Prev_State { + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private { + int prepare_calls{}; + int paint_calls{}; + void prepare_data(double_buffer::Attached auto*) { + ++prepare_calls; + } + void paint(double_buffer::Attached auto*) { + ++paint_calls; + } + }; +}; +struct Graph_Renderable : double_buffer::Impl> { + struct Prop : Prev_Prop {}; + struct State : Prev_State { + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private { + int prepare_calls{}; + int paint_calls{}; + int prepare_builds{}; + bool rebuild_prepare{}; + tf::Taskflow build_prepare_graph(double_buffer::Attached auto*, const State&) { + ++prepare_builds; + tf::Taskflow graph; + graph.emplace([this] { ++prepare_calls; }).name("test.prepare.graph.task"); + return graph; + } + bool should_rebuild_prepare_graph(double_buffer::Attached auto*, const State&) { + return std::exchange(rebuild_prepare, false); + } + void paint(double_buffer::Attached auto*) { + ++paint_calls; + } + }; +}; +using Direct = double_buffer::Attach_Object; +using Graph = double_buffer::Attach_Object; +using Scene = double_buffer::Attach_Object; +struct Dependency_State_Tag {}; +struct Dependency_Renderable : double_buffer::Impl> { + struct Prop : Prev_Prop {}; + struct State : Prev_State { + int revision{}; + bool operator==(const State&) const = default; + }; + struct Private : Prev_Private { + int prepare_calls{}; + int paint_calls{}; + bool publish_revision{}; + void prepare_data(double_buffer::Attached auto* object) { + ++prepare_calls; + if (std::exchange(publish_revision, false)) object->template update_state<&State::revision>(object->d.state.pending->revision + 1); + } + void paint(double_buffer::Attached auto*) { + ++paint_calls; + } + }; +}; +using Dependency = double_buffer::Attach_Object; +template +void add_renderable(Scene& scene, Renderable* renderable) { + ASSERT_TRUE((scene.edit_rely( + [&](auto& prepare, auto& paint) { + prepare.add(renderable); + paint.add(renderable); + } + ).has_value())); +} +} +TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) { + aethera::initialize_runtime(2); + Direct renderable; + Scene scene; + add_renderable(scene, &renderable); + scene.process([](const auto&) {}); + auto& data = static_cast(renderable.d); + auto& base = static_cast(renderable.d); + EXPECT_EQ(data.prepare_calls, 1); + EXPECT_EQ(data.paint_calls, 1); + EXPECT_EQ(base.prepare_graph, nullptr); + EXPECT_EQ(base.paint_graph, nullptr); + scene.process([](const auto&) {}); + EXPECT_EQ(data.prepare_calls, 1); + EXPECT_EQ(data.paint_calls, 1); +} +TEST(renderable_capability, graph_stage_builds_lazily_and_rebuilds_inside_condition) { + aethera::initialize_runtime(2); + Graph renderable; + Scene scene; + add_renderable(scene, &renderable); + auto& data = static_cast(renderable.d); + auto& base = static_cast(renderable.d); + EXPECT_EQ(base.prepare_graph, nullptr); + scene.process([](const auto&) {}); + ASSERT_NE(base.prepare_graph, nullptr); + EXPECT_EQ(data.prepare_builds, 1); + EXPECT_EQ(data.prepare_calls, 1); + scene.process([](const auto&) {}); + EXPECT_EQ(data.prepare_builds, 1); + EXPECT_EQ(data.prepare_calls, 1); + data.rebuild_prepare = true; + renderable.mark_dirty(); + scene.process([](const auto&) {}); + EXPECT_EQ(data.prepare_builds, 2); + EXPECT_EQ(data.prepare_calls, 2); +} +TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundaries) { + aethera::initialize_runtime(2); + Direct renderable; + Scene scene; + add_renderable(scene, &renderable); + int renderable_updates = 0; + int scene_updates = 0; + int runtime_updates = 0; + renderable.set_state_callback([&](const auto& state) { + ++renderable_updates; + EXPECT_TRUE(state.prepare_executed); + EXPECT_TRUE(state.paint_executed); + }); + scene.set_state_callback([&](const auto& state) { + ++scene_updates; + EXPECT_GT(state.taskflow_task_count, 0u); + }); + aethera::set_runtime_state_callback([&](const auto& state) { + ++runtime_updates; + EXPECT_GE(state.completed_taskflow_count, 1u); + EXPECT_GT(state.observed_task_count, 0u); + }); + scene.process([](const auto&) {}); + EXPECT_EQ(renderable_updates, 1); + EXPECT_EQ(scene_updates, 1); + EXPECT_EQ(runtime_updates, 1); + aethera::clear_runtime_state_callback(); +} +TEST(scene_condition, upstream_change_makes_downstream_run_in_same_taskflow) { + aethera::initialize_runtime(2); + Dependency source; + Dependency target; + Scene scene; + ASSERT_TRUE((scene.edit_rely( + [&](auto& prepare, auto& paint) { + prepare.add(&source); + prepare.add(&target); + prepare.template add_dependency<&Dependency_Renderable::State::revision>(&target, &source); + paint.add(&source); + paint.add(&target); + } + ).has_value())); + scene.process([](const auto&) {}); + auto& source_data = static_cast(source.d); + auto& target_data = static_cast(target.d); + EXPECT_EQ(source_data.prepare_calls, 1); + EXPECT_EQ(target_data.prepare_calls, 1); + scene.process([](const auto&) {}); + EXPECT_EQ(source_data.prepare_calls, 1); + EXPECT_EQ(target_data.prepare_calls, 1); + source_data.publish_revision = true; + source.mark_dirty(); + scene.process([](const auto&) {}); + EXPECT_EQ(source_data.prepare_calls, 2); + EXPECT_EQ(target_data.prepare_calls, 2); +}