编译过

This commit is contained in:
2026-08-20 14:45:12 +08:00
parent f379907f1b
commit 44af33bee6
14 changed files with 991 additions and 293 deletions
+42
View File
@@ -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
+130
View File
@@ -0,0 +1,130 @@
# 统一错误处理原则
## 1. 已知结果处理规则
属于 API 契约允许出现、调用方能够明确处理的情况,使用返回码表达。
返回码不等于错误码,`timeout``cancelled``not_ready``no_change` 等都可以是正常结果。
判断依据只有一个:
**该结果是否可预料,并且调用方是否具有明确的处理方式。**
不按“内部/外部”区分。
### 返回码定义规则
**每个存在返回码的函数必须拥有自己独立的返回码枚举,不得在一个类或模块中定义大而通用的公共返回码枚举供多个函数混用。**
例如:
```cpp
enum class Request_Frame_Result {
not_ready,
cancelled
};
std::expected<Frame, Request_Frame_Result> 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<T, Xxx_Result>
```
* 没有额外正常返回值,返回码本身即可完整表达结果:
```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;第三方结果进入系统后重新按同一规则分类。**
+58
View File
@@ -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 <class Data_Type, class Observer_Type>
```
* 命名空间:`lower_snake_case`
```cpp
namespace renderive::render_3d
```
* 文件名与主要类型一致:`Upper_Snake_Case`
```text
Scene_Base.hpp
Gpu_Completion_Service.cpp
Low_Latency_Strategy.inl
```
* 内部实现统一使用 `detail` 命名空间。
**总规则:类型大写下划线,值和函数小写下划线,成员变量末尾加 `_`。**
+13 -43
View File
@@ -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
"$<BUILD_INTERFACE:${Aethera_Kernel_source_dir}>"
@@ -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
"$<TARGET_FILE:TBB::tbb>"
"$<TARGET_FILE_DIR:${target}>"
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 "$<CONFIG>" -L "^Aethera_Kernel$" --output-on-failure
@@ -19,16 +19,13 @@
#include <utility>
#include <vector>
namespace double_buffer {
template <typename T>
concept Lockable = requires(T& lock) {
template <typename Lock>
concept Lockable = requires(Lock& lock) {
{ lock.lock() } -> std::same_as<void>;
{ lock.unlock() } -> std::same_as<void>;
};
template <typename T>
concept Prop_State = requires {
requires std::default_initializable<T>;
requires std::assignable_from<T&, const T&>;
};
template <typename Value>
concept Prop_State = std::default_initializable<Value> && std::assignable_from<Value&, const Value&>;
struct Pmr {
using Resource = std::pmr::memory_resource;
private:
@@ -40,9 +37,9 @@ public:
[[nodiscard]] Resource* resource() const noexcept {
return value;
}
template <typename T>
std::pmr::polymorphic_allocator<T> allocator() const noexcept {
return std::pmr::polymorphic_allocator<T>{value};
template <typename Value>
std::pmr::polymorphic_allocator<Value> allocator() const noexcept {
return std::pmr::polymorphic_allocator<Value>{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 <typename T>
// Double_Buffer 只负责交换 pending/current 两个视图,不复制数据;需要交换后同步基线的场景使用 Synchronized_Double_Buffer。
template <typename Value>
struct Double_Buffer : Pinned {
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
private:
std::tuple<T, T> buf;
std::tuple<Value, Value> 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 <typename Value, Buffer_Direction Direction>
struct Synchronized_Double_Buffer : Double_Buffer<Value> {
using Base = Double_Buffer<Value>;
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 <typename Tag, Prop_State Prev = detail::State_Root>
// State_Type 用 Tag 标记每一层状态,Prev_State 把 CRTP 继承链上的状态按层串起来,供精确回调和类型约束使用。
template <typename Tag, typename Prev = detail::State_Root>
requires Prop_State<Prev>
struct State_Type : Prev {
using Tag_Type = Tag;
using Prev_State = Prev;
bool operator==(const State_Type&) const = default;
};
template <typename Tag, Prop_State T = Tag>
template <typename Tag, typename Value = Tag>
requires Prop_State<Value>
struct Tagged_Buffer {
using Tag_Type = Tag;
using Value_Type = T;
using Value_Type = Value;
};
struct Root;
// Rely_Type 描述一类依赖图及其允许绑定的对象类型;dirty 只表示该依赖阶段需要重新处理,不承担图结构所有权。
template <typename Tag, typename Object>
struct Rely_Type {
using Tag_Type = Tag;
@@ -167,47 +192,45 @@ struct Unique_Types<First, Rest...> : std::bool_constant<
((!std::same_as<First, Rest>) && ...) &&
Unique_Types<Rest...>::value
> {};
template <typename T>
template <typename Value>
struct Is_Tagged_Buffer : std::false_type {};
template <typename Tag, Prop_State T>
struct Is_Tagged_Buffer<Tagged_Buffer<Tag, T>> : std::true_type {};
template <typename T>
template <typename Tag, typename Value>
struct Is_Tagged_Buffer<Tagged_Buffer<Tag, Value>> : std::true_type {};
template <typename Value>
struct Is_Rely_Type : std::false_type {};
template <typename Tag, typename Object>
struct Is_Rely_Type<Rely_Type<Tag, Object>> : std::true_type {};
template <typename T>
template <typename Value>
struct Is_State_Type : std::false_type {};
template <typename Tag, Prop_State Prev>
template <typename Tag, typename Prev>
struct Is_State_Type<State_Type<Tag, Prev>> : std::true_type {};
template <typename T>
concept Buffer_Type = Is_Tagged_Buffer<T>::value;
template <typename T>
concept Rely_Mechanism = Is_Rely_Type<T>::value;
template <typename T>
concept State_Mechanism = Is_State_Type<T>::value;
template <typename T>
concept Mechanism_Type = requires {
requires Buffer_Type<T> || Rely_Mechanism<T> || State_Mechanism<T>;
template <typename Value>
concept Buffer_Type = Is_Tagged_Buffer<Value>::value;
template <typename Value>
concept Rely_Mechanism = Is_Rely_Type<Value>::value;
template <typename Value>
concept State_Mechanism = Is_State_Type<Value>::value;
template <typename Value>
concept Mechanism_Type = Buffer_Type<Value> || Rely_Mechanism<Value> || State_Mechanism<Value>;
template <typename Value>
concept Tagged_State = Prop_State<Value> && requires {
typename Value::Tag_Type;
typename Value::Prev_State;
requires std::derived_from<Value, State_Type<typename Value::Tag_Type, typename Value::Prev_State>>;
};
template <typename T>
concept Tagged_State = Prop_State<T> && requires {
typename T::Tag_Type;
typename T::Prev_State;
requires std::derived_from<T, State_Type<typename T::Tag_Type, typename T::Prev_State>>;
};
template <typename State>
template <typename State_T, typename = void>
struct State_Layers {
using Type = std::tuple<>;
};
template <Tagged_State State>
struct State_Layers<State> {
template <typename State_T>
struct State_Layers<State_T, std::void_t<typename State_T::Tag_Type, typename State_T::Prev_State>> {
using Type = decltype(std::tuple_cat(
std::declval<std::tuple<State>>(),
std::declval<typename State_Layers<typename State::Prev_State>::Type>()
std::declval<std::tuple<State_T>>(),
std::declval<typename State_Layers<typename State_T::Prev_State>::Type>()
));
};
template <typename State>
using State_Layers_T = typename State_Layers<State>::Type;
template <typename State_T>
using State_Layers_T = typename State_Layers<State_T>::Type;
template <typename T>
struct Member_Pointer_Traits;
template <typename Member, typename Owner>
@@ -228,11 +251,11 @@ concept State_Member_Settable = requires(State& state, Value&& value) {
requires State_Member<Member, State>;
state.*Member = std::forward<Value>(value);
};
template <Mechanism_Type T>
template <typename T>
using Mechanism_Buffer_Tuple = std::conditional_t<Buffer_Type<T>, std::tuple<T>, std::tuple<>>;
template <Mechanism_Type T>
template <typename T>
using Mechanism_Rely_Tuple = std::conditional_t<Rely_Mechanism<T>, std::tuple<T>, std::tuple<>>;
template <Mechanism_Type T>
template <typename T>
using Mechanism_State_Tuple = std::conditional_t<State_Mechanism<T>, std::tuple<T>, std::tuple<>>;
template <typename Tag, typename Tuple>
struct Has_Tag : std::false_type {};
@@ -274,9 +297,9 @@ concept Rely_Tag_In = requires {
requires Rely_List<Tuple>;
requires Has_Tag<Tag, Tuple>::value;
};
template <typename Tag, Rely_List Tuple>
template <typename Tag, typename Tuple>
struct Rely_Type_By_Tag;
template <typename Tag, Rely_Mechanism... Relies>
template <typename Tag, typename... Relies>
struct Rely_Type_By_Tag<Tag, std::tuple<Relies...>> {
private:
template <typename First, typename... Rest>
@@ -294,9 +317,9 @@ private:
public:
using Type = typename Find<Relies...>::Type;
};
template <typename Tag, Rely_List Tuple> requires Rely_Tag_In<Tag, Tuple>
template <typename Tag, typename Tuple>
using Rely_Declaration = typename Rely_Type_By_Tag<Tag, Tuple>::Type;
template <typename Tag, Rely_List Tuple> requires Rely_Tag_In<Tag, Tuple>
template <typename Tag, typename Tuple>
using Rely_Bound_Object = typename Rely_Declaration<Tag, Tuple>::Object_Type;
template <typename Tuple>
struct Is_State_List : std::false_type {};
@@ -307,16 +330,16 @@ struct Is_State_List<std::tuple<States...>> : Tagged_List_Check<
> {};
template <typename Tuple>
concept State_List = Is_State_List<Tuple>::value;
template <typename State>
concept State_Chain = Tagged_State<State> && State_List<State_Layers_T<State>>;
template <typename State_T>
concept State_Chain = Tagged_State<State_T> && State_List<State_Layers_T<State_T>>;
template <typename Tag, typename Tuple>
concept State_Tag_In = requires {
requires State_List<Tuple>;
requires Has_Tag<Tag, Tuple>::value;
};
template <typename State, typename Tuple>
concept State_Chain_Matches = State_Chain<State> && State_List<Tuple> && [] {
using Layers = State_Layers_T<State>;
template <typename State_T, typename Tuple>
concept State_Chain_Matches = State_Chain<State_T> && State_List<Tuple> && [] {
using Layers = State_Layers_T<State_T>;
if constexpr (std::tuple_size_v<Layers> != std::tuple_size_v<Tuple>) return false;
else {
return []<std::size_t... I>(std::index_sequence<I...>) {
@@ -327,86 +350,84 @@ concept State_Chain_Matches = State_Chain<State> && State_List<Tuple> && [] {
}(std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}
}();
template <State_Mechanism State, Prop_State Prev>
template <typename State_T, typename Prev>
struct Rebind_State;
template <typename Tag, Prop_State Marker_Prev, Prop_State Prev>
template <typename Tag, typename Marker_Prev, typename Prev>
struct Rebind_State<State_Type<Tag, Marker_Prev>, Prev> {
using Type = State_Type<Tag, Prev>;
};
template <State_Mechanism State, Prop_State Prev>
using Rebind_State_T = typename Rebind_State<State, Prev>::Type;
template <typename Tag, typename... States>
consteval std::size_t state_tag_index() {
std::size_t result{};
std::size_t current{};
((std::same_as<Tag, typename States::Tag_Type> ? result = current : result, ++current), ...);
return result;
template <typename State_T, typename Prev>
using Rebind_State_T = typename Rebind_State<State_T, Prev>::Type;
// Tag 是否存在由外层 requires 保证;内部只按声明顺序计算位置,避免每层重复检查,也避开 MSVC 对复杂 fold 表达式的解析问题。
template <typename Tag, typename First, typename... Rest>
consteval std::size_t tag_index() {
if constexpr (std::is_same_v<Tag, typename First::Tag_Type>) return 0;
else if constexpr (sizeof...(Rest) == 0) return 0;
else return 1 + tag_index<Tag, Rest...>();
}
template <typename Tag, typename State>
struct State_By_Tag;
template <typename Tag, Tagged_State State>
struct State_By_Tag<Tag, State> {
template <typename Tag, typename Tuple>
struct Tag_Index;
template <typename Tag, typename... Types>
struct Tag_Index<Tag, std::tuple<Types...>> : std::integral_constant<std::size_t, tag_index<Tag, Types...>()> {};
template <typename Tag, typename Tuple>
inline constexpr std::size_t tag_index_v = Tag_Index<Tag, Tuple>::value;
template <typename Tag, typename State_T>
struct State_By_Tag {
private:
using Layers = State_Layers_T<State>;
template <std::size_t... I>
static consteval std::size_t index(std::index_sequence<I...>) {
std::size_t result{};
((void)(std::same_as<Tag, typename std::tuple_element_t<I, Layers>::Tag_Type> ? result = I : result), ...);
return result;
}
using Layers = State_Layers_T<State_T>;
public:
using Type = std::tuple_element_t<index(std::make_index_sequence<std::tuple_size_v<Layers>>{}), Layers>;
using Type = std::tuple_element_t<tag_index_v<Tag, Layers>, Layers>;
};
template <typename Tag, Tagged_State State>
using State_Value = typename State_By_Tag<Tag, State>::Type;
template <typename Callback, typename Tag, typename State, typename States>
concept State_Callback_For = State_Tag_In<Tag, States> && State_Chain_Matches<State, States> && std::invocable<Callback, const State_Value<Tag, State>&>;
template <typename Tag, typename State_T>
using State_Value = typename State_By_Tag<Tag, State_T>::Type;
template <typename Callback, typename Tag, typename State_T, typename States>
concept State_Callback_For = State_Tag_In<Tag, States> && State_Chain_Matches<State_T, States> && std::invocable<Callback, const State_Value<Tag, State_T>&>;
template <typename Tuple>
struct State_Callback_Storage_Impl;
template <typename... States>
struct State_Callback_Storage_Impl<std::tuple<States...>> {
struct State_Callback_Tuple;
template <typename... Layers>
struct State_Callback_Tuple<std::tuple<Layers...>> {
using Type = std::tuple<std::function<void(const Layers&)>...>;
};
template <typename State_T>
struct State_Callback_Storage {
private:
std::tuple<std::function<void(const States&)>...> callbacks;
using Layers = State_Layers_T<State_T>;
using Callbacks = typename State_Callback_Tuple<Layers>::Type;
Callbacks callbacks;
template <typename Tag>
static consteval std::size_t index() {
return tag_index_v<Tag, Layers>;
}
public:
template <typename Tag, typename Callback> requires
(std::same_as<Tag, typename States::Tag_Type> || ...)
template <typename Tag, typename Callback> requires State_Tag_In<Tag, Layers>
void set(Callback&& callback) {
constexpr auto index = state_tag_index<Tag, States...>();
using State = std::tuple_element_t<index, std::tuple<States...>>;
std::get<index>(callbacks) = std::function<void(const State&)>{std::forward<Callback>(callback)};
constexpr auto value_index = index<Tag>();
using Layer = std::tuple_element_t<value_index, Layers>;
std::get<value_index>(callbacks) = std::function<void(const Layer&)>{std::forward<Callback>(callback)};
}
template <typename Tag> requires (std::same_as<Tag, typename States::Tag_Type> || ...)
template <typename Tag> requires State_Tag_In<Tag, Layers>
void clear() {
constexpr auto index = state_tag_index<Tag, States...>();
std::get<index>(callbacks) = {};
std::get<index<Tag>()>(callbacks) = {};
}
template <typename Tag, Tagged_State State> requires (std::same_as<Tag, typename States::Tag_Type> || ...)
void notify(const State& state) {
constexpr auto index = state_tag_index<Tag, States...>();
using Layer = std::tuple_element_t<index, std::tuple<States...>>;
auto& callback = std::get<index>(callbacks);
template <typename Tag> requires State_Tag_In<Tag, Layers>
void notify(const State_T& state) {
constexpr auto value_index = index<Tag>();
using Layer = std::tuple_element_t<value_index, Layers>;
auto& callback = std::get<value_index>(callbacks);
if (callback) callback(static_cast<const Layer&>(state));
}
};
template <Tagged_State State>
using State_Callback_Storage = State_Callback_Storage_Impl<State_Layers_T<State>>;
template <typename Tag, Mechanism_Type... Types>
consteval std::size_t tag_index() {
std::size_t result{};
std::size_t current{};
((std::same_as<Tag, typename Types::Tag_Type> ? result = current : result, ++current), ...);
return result;
}
template <typename Tag, Buffer_List Tuple>
// 每个 State Tag 独立保存回调;字段更新不会隐式通知,发布频率由拥有该状态的内部机制显式调用 notify 控制。
template <typename Tag, typename Tuple>
struct Buffer_Value_By_Tag;
template <typename Tag, Buffer_Type... Buffers>
template <typename Tag, typename... Buffers>
struct Buffer_Value_By_Tag<Tag, std::tuple<Buffers...>> {
using Type = typename std::tuple_element_t<
tag_index<Tag, Buffers...>(),
std::tuple<Buffers...>
>::Value_Type;
};
template <typename Tag, Buffer_List Tuple>
template <typename Tag, typename Tuple>
using Buffer_Value = typename Buffer_Value_By_Tag<Tag, Tuple>::Type;
template <typename Value, typename Tag, typename Tuple>
concept Buffer_Value_Settable = requires(Buffer_Value<Tag, Tuple>& target, Value&& value) {
@@ -414,25 +435,26 @@ concept Buffer_Value_Settable = requires(Buffer_Value<Tag, Tuple>& target, Value
requires Buffer_Tag_In<Tag, Tuple>;
target = std::forward<Value>(value);
};
template <Buffer_List Tuple>
// Buffer_Storage 保存普通 Tagged_Buffer 的双缓冲;它只执行基础交换,不做 State/Prop 那种交换后同步。
template <typename Tuple>
struct Buffer_Storage;
template <Buffer_Type... Buffers>
template <typename... Buffers>
struct Buffer_Storage<std::tuple<Buffers...>> {
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
private:
std::tuple<Double_Buffer<typename Buffers::Value_Type>...> buffers;
template <Buffer_Tag_In<std::tuple<Buffers...>> Tag>
template <typename Tag>
static consteval std::size_t index() {
return tag_index<Tag, Buffers...>();
}
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 <Buffer_Tag_In<std::tuple<Buffers...>> Tag>
template <typename Tag> requires Buffer_Tag_In<Tag, std::tuple<Buffers...>>
auto& get() {
return std::get<index<Tag>()>(buffers);
}
template <Buffer_Tag_In<std::tuple<Buffers...>> Tag>
template <typename Tag> requires Buffer_Tag_In<Tag, std::tuple<Buffers...>>
const auto& get() const {
return std::get<index<Tag>()>(buffers);
}
@@ -475,9 +497,9 @@ concept Object_Core = requires {
requires detail::Rely_List<typename T::Relies>;
};
namespace detail {
template <Rely_List Tuple>
template <typename Tuple>
struct Rely_Storage;
template <Rely_List Tuple>
template <typename Tuple>
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<detail::Pmr_Resource*>(&pmr_resource);
}
@@ -23,26 +23,26 @@ concept Object = requires {
requires std::derived_from<typename T::Private, typename T::Prev_Private>;
};
namespace detail {
template <Object_Root Base, Mechanism_Type... Local_Mechanisms>
template <typename Base, typename... Local_Mechanisms>
using Impl_Buffers = decltype(std::tuple_cat(
std::declval<typename Base::Buffers>(),
std::declval<Mechanism_Buffer_Tuple<Local_Mechanisms>>()...
));
template <Object_Root Base, Mechanism_Type... Local_Mechanisms>
template <typename Base, typename... Local_Mechanisms>
using Impl_Relies = decltype(std::tuple_cat(
std::declval<typename Base::Relies>(),
std::declval<Mechanism_Rely_Tuple<Local_Mechanisms>>()...
));
template <Mechanism_Type... Local_Mechanisms>
template <typename... Local_Mechanisms>
using Local_States = decltype(std::tuple_cat(
std::declval<Mechanism_State_Tuple<Local_Mechanisms>>()...
));
template <Object_Root Base, Mechanism_Type... Local_Mechanisms>
template <typename Base, typename... Local_Mechanisms>
using Impl_States = decltype(std::tuple_cat(
std::declval<typename Base::States>(),
std::declval<Local_States<Local_Mechanisms...>>()
));
template <Object_Root Base, Mechanism_Type... Local_Mechanisms>
template <typename Base, typename... Local_Mechanisms>
using Impl_State_Base = Rebind_State_T<
std::tuple_element_t<0, Local_States<Local_Mechanisms...>>,
typename Base::State
@@ -64,7 +64,8 @@ concept Attached = requires {
requires Object_Root<typename T::Attached_Object>;
requires std::derived_from<T, typename T::Attached_Object>;
};
template <typename Self, Object_Root Base, detail::Mechanism_Type... Local_Mechanisms> requires
// Impl 在编译期把 Buffer/Rely/State 三类机制叠加到继承链;每一层只声明自己的机制,最终类型汇总完整能力。
template <typename Self, typename Base, typename... Local_Mechanisms> requires
detail::Impl_Mechanisms<Base, Local_Mechanisms...>
struct Impl : Base {
using This_Object = Self;
@@ -81,18 +82,18 @@ struct Impl : Base {
using Relies = detail::Impl_Relies<Base, Local_Mechanisms...>;
using States = detail::Impl_States<Base, Local_Mechanisms...>;
struct Private : Base_Private {
template <typename Owner, typename Member, Prop_State State>
void before_state_set(Self*, Member Owner::*, State*) {}
template <typename Owner, typename Member, Prop_State State>
void after_state_set(Self*, Member Owner::*, State*) {}
template <Prop_State Prop, Prop_State State>
void before_exchange(Self*, Prop*, State*, const Prop*, const State*) {}
template <Prop_State Prop, Prop_State State>
void after_exchange(Self*, Prop*, State*, const Prop*, const State*) {}
template <typename Owner, typename Member, typename State_T>
void before_state_set(Self*, Member Owner::*, State_T*) {}
template <typename Owner, typename Member, typename State_T>
void after_state_set(Self*, Member Owner::*, State_T*) {}
template <typename Prop_T, typename State_T>
void before_exchange(Self*, Prop_T*, State_T*, const Prop_T*, const State_T*) {}
template <typename Prop_T, typename State_T>
void after_exchange(Self*, Prop_T*, State_T*, const Prop_T*, const State_T*) {}
};
using Prev_Private = Private;
template <bool Reverse, Object_Core Layer, Attached Object, typename Callback>
static void walk_private(Object* object, Callback& callback) {
template <bool Reverse, typename Layer, typename Object_T, typename Callback>
static void walk_private(Object_T* object, Callback& callback) {
if constexpr (requires { typename Layer::Prev_Object; }) {
if constexpr (!Reverse) walk_private<Reverse, typename Layer::Prev_Object>(object, callback);
callback(static_cast<typename Layer::Private&>(object->d));
@@ -100,7 +101,8 @@ struct Impl : Base {
}
}
};
template <Object_Root Obj, Lockable Lock = std::mutex>
// Attach_Object 是对象运行时唯一数据入口:Prop 向外发布,State 向内提交,普通 Buffer 只交换,Rely 负责依赖图同步。
template <typename Obj, typename Lock = std::mutex> requires Object_Root<Obj> && Lockable<Lock>
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<Attach_Object>;
using Attached_Object = Obj;
struct Private : Obj::Private, Double_Buffer<Prop> {
struct Private : Obj::Private, Synchronized_Double_Buffer<Prop, Buffer_Direction::outward> {
using Allocator = std::pmr::polymorphic_allocator<std::byte>;
Double_Buffer<State> state;
detail::State_Callback_Storage<State> state_callbacks;
Synchronized_Double_Buffer<typename Obj::State, Buffer_Direction::inward> state;
detail::State_Callback_Storage<typename Obj::State> state_callbacks;
detail::Buffer_Storage<Buffers> buffer_storage;
detail::Rely_Storage<Relies> rely_storage;
explicit Private(std::pmr::memory_resource* resource) : Double_Buffer<Prop>(std::allocator_arg, Allocator{resource}),
explicit Private(std::pmr::memory_resource* resource) : Synchronized_Double_Buffer<Prop, Buffer_Direction::outward>(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 <bool Reverse, Object_Core Layer, typename Callback>
template <bool Reverse, typename Layer, typename Callback>
void walk_private(Callback& callback) {
if constexpr (requires { typename Layer::Prev_Object; }) {
if constexpr (!Reverse) walk_private<Reverse, typename Layer::Prev_Object>(callback);
@@ -175,45 +177,52 @@ private:
}
void exchange_unlocked() {
before_exchange();
static_cast<Double_Buffer<Prop>&>(d).exchange();
// State 从 pending 向内部 current 提交;Prop 从内部 current 向 pending 发布,两者交换后的写入侧都同步为最新基线。
static_cast<Synchronized_Double_Buffer<Prop, Buffer_Direction::outward>&>(d).exchange();
d.state.exchange();
d.buffer_storage.exchange();
d.rely_storage.exchange();
after_exchange();
}
public:
template <detail::Buffer_Tag_In<Buffers> Tag>
template <typename Tag> requires detail::Buffer_Tag_In<Tag, Buffers>
auto& buffer() {
this->emit_rely_source(detail::rely_id<detail::Buffer_Rely_Key<Tag>>());
return *d.buffer_storage.template get<Tag>().pending;
}
template <detail::Buffer_Tag_In<Buffers> Tag>
template <typename Tag> requires detail::Buffer_Tag_In<Tag, Buffers>
const auto& current_buffer() const {
return *d.buffer_storage.template get<Tag>().current;
}
template <detail::Buffer_Tag_In<Buffers> Tag, detail::Buffer_Value_Settable<Tag, Buffers> Value>
template <typename Tag, typename Value> requires
detail::Buffer_Tag_In<Tag, Buffers> && detail::Buffer_Value_Settable<Value, Tag, Buffers>
void set_initial_buffer(Value&& value) {
auto& buffer = d.buffer_storage.template get<Tag>();
*buffer.pending = std::forward<Value>(value);
*buffer.current = *buffer.pending;
}
template <detail::Rely_Tag_In<Relies> Tag>
template <typename Tag> requires detail::Rely_Tag_In<Tag, Relies>
[[nodiscard]] auto rely() const {
using Object = detail::Rely_Bound_Object<Tag, Relies>;
return d.rely_storage.template get<Tag>().pending->template typed_view<Object>();
using Bound_Object = detail::Rely_Bound_Object<Tag, Relies>;
const Rely* graph = d.rely_storage.template get<Tag>().pending;
return graph->typed_view<Bound_Object>();
}
template <detail::Rely_Tag_In<Relies> Tag>
template <typename Tag> requires detail::Rely_Tag_In<Tag, Relies>
[[nodiscard]] auto current_rely() const {
using Object = detail::Rely_Bound_Object<Tag, Relies>;
return d.rely_storage.template get<Tag>().current->template typed_view<Object>();
using Bound_Object = detail::Rely_Bound_Object<Tag, Relies>;
const Rely* graph = d.rely_storage.template get<Tag>().current;
return graph->typed_view<Bound_Object>();
}
template <detail::Rely_Tag_In<Relies>... Tags, typename Callback> requires
detail::Unique_Types<Tags...>::value && detail::Rely_Access_Callback_For<Callback, Relies, Tags...>
template <typename... Tags, typename Callback> requires
(detail::Rely_Tag_In<Tags, Relies> && ...) && detail::Unique_Types<Tags...>::value &&
detail::Rely_Access_Callback_For<Callback, Relies, Tags...>
void access_rely(Callback&& callback) {
d.rely_storage.template access<Tags...>(std::forward<Callback>(callback));
}
template <detail::Rely_Tag_In<Relies>... Tags, typename Callback> requires
detail::Unique_Types<Tags...>::value && detail::Rely_Edit_Callback_For<Callback, Relies, Tags...>
// 依赖图只允许在编辑侧修改;回调完成后先验证 DAG,再把新结构提交到 pending。
template <typename... Tags, typename Callback> requires
(detail::Rely_Tag_In<Tags, Relies> && ...) && detail::Unique_Types<Tags...>::value &&
detail::Rely_Edit_Callback_For<Callback, Relies, Tags...>
std::expected<void, Rely_Error> edit_rely(Callback&& callback) {
std::lock_guard<Lock> guard(lock);
return d.rely_storage.template edit<Tags...>(std::forward<Callback>(callback));
@@ -221,42 +230,47 @@ public:
std::expected<void, Rely_Error> validate_rely() const {
return d.rely_storage.validate_pending();
}
template <detail::Rely_Tag_In<Relies> Tag, typename Callback> requires
template <typename Tag, typename Callback> requires
detail::Rely_Tag_In<Tag, Relies> &&
detail::Rely_View_Node_Callback<Callback, detail::Rely_Bound_Object<Tag, Relies>>
std::expected<void, Rely_Error> for_each_rely_topological(Callback&& callback) const {
return current_rely<Tag>().for_each_topological_view(std::forward<Callback>(callback));
}
template <detail::Rely_Current_Callback Callback>
template <typename Callback> requires detail::Rely_Current_Callback<Callback>
void for_each_current_rely(Callback&& callback) const {
d.rely_storage.for_each_current(std::forward<Callback>(callback));
}
template <typename Owner, typename Member, detail::Prop_Member_Settable<Prop, Owner, Member> Value>
template <typename Owner, typename Member, typename Value> requires
detail::Prop_Member_Settable<Value, Prop, Owner, Member>
Attach_Object& set(Member Owner::* member, Value&& value) {
std::lock_guard<Lock> guard(lock);
d.pending->*member = std::forward<Value>(value);
d.current->*member = std::forward<Value>(value);
return *this;
}
template <typename Tag, detail::State_Callback_For<Tag, State, States> Callback>
template <typename Tag, typename Callback> requires
detail::State_Callback_For<Callback, Tag, State, States>
void set_state_callback(Callback&& callback) {
std::lock_guard<Lock> guard(lock);
d.state_callbacks.template set<Tag>(std::forward<Callback>(callback));
}
template <detail::State_Tag_In<States> Tag>
template <typename Tag> requires detail::State_Tag_In<Tag, States>
void clear_state_callback() {
std::lock_guard<Lock> guard(lock);
d.state_callbacks.template clear<Tag>();
}
template <typename Tag, detail::State_Callback_For<Tag, State, States> Callback>
template <typename Tag, typename Callback> requires
detail::State_Callback_For<Callback, Tag, State, States>
void access_state(Callback&& callback) const {
std::lock_guard<Lock> guard(lock);
using Layer = detail::State_Value<Tag, State>;
std::invoke(std::forward<Callback>(callback), static_cast<const Layer&>(*d.state.current));
}
template <detail::State_Tag_In<States> Tag>
template <typename Tag> requires detail::State_Tag_In<Tag, States>
void notify_state() {
d.state_callbacks.template notify<Tag>(*d.state.current);
}
template <auto Member, detail::State_Member_Settable<Member, State> Value>
// State 写入 pending,随后向依赖图发出对应成员的 dirty 信号;真正进入 current 发生在 exchange/process 边界。
template <auto Member, typename Value> requires detail::State_Member_Settable<Value, Member, State>
void update_state(Value&& value) {
std::lock_guard<Lock> guard(lock);
before_state_set(Member);
@@ -276,7 +290,7 @@ public:
std::lock_guard<Lock> guard(lock);
exchange_unlocked();
}
template <std::invocable<const State&> Callback>
template <typename Callback> requires std::invocable<Callback, const State&>
void exchange(Callback&& callback) {
std::lock_guard<Lock> guard(lock);
exchange_unlocked();
@@ -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<Edge> find_edges(Root* target, Root* source) const {
return rely->find_edges(target, source);
}
template <std::invocable<const Edge&> Callback>
template <typename Callback> requires std::invocable<Callback, const Edge&>
void for_each_edge(Callback&& callback) const {
rely->for_each_edge(std::forward<Callback>(callback));
}
};
template <Root_Derived Object>
template <typename Object> requires Root_Derived<Object>
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 <std::invocable<Object*, Private_Type&> Callback>
template <typename Callback> requires std::invocable<Callback, Object*, Private_Type&>
void for_each_bound(Callback&& callback) const {
this->rely->for_each(
[&](const Node& node) {
@@ -103,7 +104,7 @@ public:
}
);
}
template <std::invocable<const Typed_View&, const Node&> Callback>
template <typename Callback> requires std::invocable<Callback, const Typed_View&, const Node&>
std::expected<void, Error> for_each_topological_view(Callback&& callback) const {
return this->rely->for_each_topological(
[&](const Node& node) {
@@ -111,12 +112,13 @@ public:
}
);
}
template <std::invocable<const Node&> Callback>
template <typename Callback> requires std::invocable<Callback, const Node&>
void for_each(Callback&& callback) const {
this->rely->for_each(std::forward<Callback>(callback));
}
};
template <Root_Derived Bound_Object>
// Editor 只操作待提交图;结构是否合法由编辑完成后的拓扑验证统一判断,不在每个底层操作重复检查。
template <typename Bound_Object> requires Root_Derived<Bound_Object>
class Editor : public View {
private:
Rely* edit_rely;
@@ -134,18 +136,18 @@ public:
void clear() {
edit_rely->reset();
}
template <detail::Bound_Rely_Object Object> requires std::derived_from<Object, Bound_Object>
template <typename Object> requires detail::Bound_Rely_Object<Object> && std::derived_from<Object, Bound_Object>
Node* add(Object* object) {
return edit_rely->template add_node<Bound_Object>(object, bind_node_data);
}
template <detail::Bound_Rely_Object Object> requires std::derived_from<Object, Bound_Object>
template <typename Object> requires detail::Bound_Rely_Object<Object> && std::derived_from<Object, Bound_Object>
void add(std::initializer_list<Object*> objects) {
for (auto* object : objects) edit_rely->template add_node<Bound_Object>(object, bind_node_data);
}
bool remove(Root* object) {
return edit_rely->remove_node(object);
}
template <Root_Derived Target> requires std::derived_from<Target, Bound_Object>
template <typename Target> requires Root_Derived<Target> && std::derived_from<Target, Bound_Object>
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 <auto Member, detail::Bound_Rely_Object Target, detail::State_Rely_Source<Member> Source> requires
template <auto Member, typename Target, typename Source> requires
detail::Bound_Rely_Object<Target> && detail::State_Rely_Source<Source, Member> &&
std::derived_from<Target, Bound_Object>
Node* add_dependency(Target* target, Source* source) {
return edit_rely->template add_dependency_runtime<Bound_Object, Target, Source>(
@@ -167,7 +170,8 @@ public:
bind_node_data
);
}
template <typename Buffer_Tag, detail::Bound_Rely_Object Target, detail::Buffer_Rely_Source<Buffer_Tag> Source> requires
template <typename Buffer_Tag, typename Target, typename Source> requires
detail::Bound_Rely_Object<Target> && detail::Buffer_Rely_Source<Source, Buffer_Tag> &&
std::derived_from<Target, Bound_Object>
Node* add_dependency(Target* target, Source* source) {
return edit_rely->template add_dependency_runtime<Bound_Object, Target, Source>(
@@ -179,7 +183,8 @@ public:
bind_node_data
);
}
template <typename Source_Tag, detail::Bound_Rely_Object Target, detail::Rely_Object Source> requires
template <typename Source_Tag, typename Target, typename Source> requires
detail::Bound_Rely_Object<Target> && detail::Rely_Object<Source> &&
std::derived_from<Target, Bound_Object>
Node* add_dirty_dependency(Target* target, Source* source) {
return edit_rely->template add_dependency_runtime<Bound_Object, Target, Source>(
@@ -191,25 +196,28 @@ public:
bind_node_data
);
}
template <auto Member, detail::Bound_Rely_Object Target, detail::State_Rely_Source<Member> Source> requires
template <auto Member, typename Target, typename Source> requires
detail::Bound_Rely_Object<Target> && detail::State_Rely_Source<Source, Member> &&
std::derived_from<Target, Bound_Object>
bool remove_dependency(Target* target, Source* source) {
return edit_rely->remove_edge(target, source, detail::rely_id<detail::State_Rely_Key<Member>>(), target_tag);
}
template <typename Buffer_Tag, detail::Bound_Rely_Object Target, detail::Buffer_Rely_Source<Buffer_Tag> Source> requires
template <typename Buffer_Tag, typename Target, typename Source> requires
detail::Bound_Rely_Object<Target> && detail::Buffer_Rely_Source<Source, Buffer_Tag> &&
std::derived_from<Target, Bound_Object>
bool remove_dependency(Target* target, Source* source) {
return edit_rely->remove_edge(target, source, detail::rely_id<detail::Buffer_Rely_Key<Buffer_Tag>>(), target_tag);
}
template <Root_Derived Target> requires std::derived_from<Target, Bound_Object>
template <typename Target> requires Root_Derived<Target> && std::derived_from<Target, Bound_Object>
bool remove_dependency(Target* target, Root* source) {
return edit_rely->remove_dependency_impl(target, source);
}
};
private:
template <detail::Rely_List Tuple>
friend struct Root;
template <typename Tuple>
friend struct detail::Rely_Storage;
template <detail::Rely_List Tuple>
template <typename Tuple>
friend struct detail::Rely_Build_Storage;
std::pmr::memory_resource* pmr_resource;
std::pmr::list<Node> list;
@@ -218,6 +226,7 @@ private:
std::pmr::unordered_multimap<Dirty_Source, Dirty_Target, Dirty_Source_Hash> 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 <std::invocable<const Edge&> Callback>
template <typename Callback> requires std::invocable<Callback, const Edge&>
void for_each_edge(Callback&& callback) const {
for (const auto& edge : edges) std::invoke(callback, edge);
}
// 拓扑结果既用于验证无环,也定义上层按照依赖顺序构图和遍历的稳定入口。
std::expected<std::vector<const Node*>, Error> topological_order() const {
enum class Visit {
none,
@@ -583,18 +601,18 @@ public:
}
return result;
}
template <std::invocable<const Node&> Callback>
template <typename Callback> requires std::invocable<Callback, const Node&>
void for_each(Callback&& callback) const {
for (const auto& node : list) std::invoke(callback, node);
}
template <std::invocable<const Node&> Callback>
template <typename Callback> requires std::invocable<Callback, const Node&>
std::expected<void, Error> 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 <std::invocable<const View&, const Node&> Callback>
template <typename Callback> requires std::invocable<Callback, const View&, const Node&>
std::expected<void, Error> 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*>(rely)->detach_destroyed(this);
}
}
}
@@ -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 <Rely_List Tuple>
template <typename Tuple>
struct Rely_Storage;
template <Rely_Mechanism... Relies>
// Rely_Storage 为每个 Rely_Type 保存 pending/current 两份图;编辑只落到 pendingexchange 后 current 成为执行侧并同步新的编辑基线。
template <typename... Relies>
struct Rely_Storage<std::tuple<Relies...>> {
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
private:
template <Rely_Mechanism Mechanism>
struct Rely_Buffer : Double_Buffer<Rely>, Mechanism {
using Base = Double_Buffer<Rely>;
template <typename Mechanism>
struct Rely_Buffer : Synchronized_Double_Buffer<Rely, Buffer_Direction::inward>, Mechanism {
using Base = Synchronized_Double_Buffer<Rely, Buffer_Direction::inward>;
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<Rely_Buffer<Relies>...> relies;
template <Rely_Tag_In<std::tuple<Relies...>> Tag>
void pair_buffers() {
std::apply(
[](auto&... buffer) {
(buffer.pending->pair_with(*buffer.current), ...);
},
relies
);
}
template <typename Tag>
static consteval std::size_t index() {
return tag_index<Tag, Relies...>();
}
@@ -56,35 +65,40 @@ private:
return validate_pending_impl<I + 1>();
}
}
template <Rely_Mechanism Mechanism>
template <typename Mechanism>
static void exchange_one(Rely_Buffer<Mechanism>& 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 <Rely_Tag_In<std::tuple<Relies...>> Tag>
Rely_Storage(std::allocator_arg_t, const allocator_type& allocator) : relies(std::allocator_arg, allocator) {
pair_buffers();
}
template <typename Tag> requires Rely_Tag_In<Tag, std::tuple<Relies...>>
auto& get() {
return std::get<index<Tag>()>(relies);
}
template <Rely_Tag_In<std::tuple<Relies...>> Tag>
template <typename Tag> requires Rely_Tag_In<Tag, std::tuple<Relies...>>
const auto& get() const {
return std::get<index<Tag>()>(relies);
}
template <Rely_Tag_In<std::tuple<Relies...>>... Tags, typename Callback> requires
Unique_Types<Tags...>::value && Rely_Access_Callback_For<Callback, std::tuple<Relies...>, Tags...>
template <typename... Tags, typename Callback> requires
(Rely_Tag_In<Tags, std::tuple<Relies...>> && ...) && Unique_Types<Tags...>::value &&
Rely_Access_Callback_For<Callback, std::tuple<Relies...>, Tags...>
void access(Callback&& callback) {
std::invoke(
std::forward<Callback>(callback),
static_cast<Rely_Declaration<Tags, std::tuple<Relies...>>&>(get<Tags>())...
);
}
template <Rely_Tag_In<std::tuple<Relies...>>... Tags, typename Callback> requires
Unique_Types<Tags...>::value && Rely_Edit_Callback_For<Callback, std::tuple<Relies...>, Tags...>
// 编辑采用临时图事务:先复制 pending,回调修改临时图并统一验证,成功后才替换 pending,失败不会污染现有结构。
template <typename... Tags, typename Callback> requires
(Rely_Tag_In<Tags, std::tuple<Relies...>> && ...) && Unique_Types<Tags...>::value &&
Rely_Edit_Callback_For<Callback, std::tuple<Relies...>, Tags...>
std::expected<void, Rely_Error> edit(Callback&& callback) {
std::tuple<std::conditional_t<true, Rely, Tags>...> next(*get<Tags>().pending...);
auto editors = [&]<std::size_t... I>(std::index_sequence<I...>) {
@@ -156,12 +170,13 @@ public:
);
}
};
template <Rely_Mechanism... Relies>
// Builder 使用独立依赖图完成对象构造期编辑;build 成功时一次性提交到对象的正式 Rely_Storage。
template <typename... Relies>
struct Rely_Build_Storage<std::tuple<Relies...>> {
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
private:
std::tuple<std::conditional_t<true, Rely, Relies>...> relies;
template <Rely_Tag_In<std::tuple<Relies...>> Tag>
template <typename Tag>
static consteval std::size_t index() {
return tag_index<Tag, Relies...>();
}
@@ -176,7 +191,7 @@ private:
return validate_impl<I + 1>();
}
}
template <Rely_Mechanism Rely_Type>
template <typename Rely_Type>
void commit_one(Rely_Storage<std::tuple<Relies...>>& storage) {
using Tag = typename Rely_Type::Tag_Type;
auto& buffer = storage.template get<Tag>();
@@ -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 <Rely_Tag_In<std::tuple<Relies...>> Tag>
template <typename Tag> requires Rely_Tag_In<Tag, std::tuple<Relies...>>
Rely& get() {
return std::get<index<Tag>()>(relies);
}
template <Rely_Tag_In<std::tuple<Relies...>> Tag>
template <typename Tag> requires Rely_Tag_In<Tag, std::tuple<Relies...>>
const Rely& get() const {
return std::get<index<Tag>()>(relies);
}
template <Rely_Tag_In<std::tuple<Relies...>>... Tags, typename Callback> requires
Unique_Types<Tags...>::value && Rely_Edit_Callback_For<Callback, std::tuple<Relies...>, Tags...>
template <typename... Tags, typename Callback> requires
(Rely_Tag_In<Tags, std::tuple<Relies...>> && ...) && Unique_Types<Tags...>::value &&
Rely_Edit_Callback_For<Callback, std::tuple<Relies...>, Tags...>
void edit(Callback&& callback) {
auto editors = std::tuple{
Rely::Editor<Rely_Bound_Object<Tags, std::tuple<Relies...>>>(
@@ -241,25 +257,30 @@ struct Root::Builder {
Builder& set_pmr(Pmr::Resource* resource) {
return set_pmr(Pmr{resource});
}
template <typename Owner, typename Member, detail::Prop_Member_Settable<Prop, Owner, Member> Value>
template <typename Owner, typename Member, typename Value> requires
detail::Prop_Member_Settable<Value, Prop, Owner, Member>
Builder& set(Member Owner::* member, Value&& value) {
object->set(member, std::forward<Value>(value));
return *this;
}
template <detail::Buffer_Tag_In<typename Object::Buffers> Tag, detail::Buffer_Value_Settable<Tag, typename Object::Buffers> Value>
template <typename Tag, typename Value> requires
detail::Buffer_Tag_In<Tag, typename Object::Buffers> &&
detail::Buffer_Value_Settable<Value, Tag, typename Object::Buffers>
Builder& set(Value&& value) {
object->template set_initial_buffer<Tag>(std::forward<Value>(value));
return *this;
}
template <detail::Rely_Tag_In<typename Object::Relies>... Rely_Tags, typename Callback> requires
template <typename... Rely_Tags, typename Callback> requires
(detail::Rely_Tag_In<Rely_Tags, typename Object::Relies> && ...) &&
detail::Unique_Types<Rely_Tags...>::value &&
detail::Rely_Edit_Callback_For<Callback, typename Object::Relies, Rely_Tags...>
Builder& edit_rely(Callback&& callback) {
rely_storage.template edit<Rely_Tags...>(std::forward<Callback>(callback));
return *this;
}
template <detail::Rely_Tag_In<typename Object::Relies> Rely_Tag, detail::Bound_Rely_Object Node_Object> requires
(std::derived_from<Node_Object, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>)
template <typename Rely_Tag, typename Node_Object> requires
detail::Rely_Tag_In<Rely_Tag, typename Object::Relies> && detail::Bound_Rely_Object<Node_Object> &&
std::derived_from<Node_Object, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>
Builder& add_dependency_node(Node_Object* node) {
return edit_rely<Rely_Tag>(
[&](auto& editor) {
@@ -267,7 +288,8 @@ struct Root::Builder {
}
);
}
template <detail::Rely_Tag_In<typename Object::Relies> Rely_Tag, Root_Derived Node_Object>
template <typename Rely_Tag, typename Node_Object> requires
detail::Rely_Tag_In<Rely_Tag, typename Object::Relies> && Root_Derived<Node_Object>
Builder& remove_dependency_node(Node_Object* node) {
return edit_rely<Rely_Tag>(
[&](auto& editor) {
@@ -275,12 +297,10 @@ struct Root::Builder {
}
);
}
template <
detail::Rely_Tag_In<typename Object::Relies> Rely_Tag,
auto Member,
detail::Rely_Object Target,
detail::State_Rely_Source<Member> Source> requires
(detail::Bound_Rely_Object<Target> && std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>)
template <typename Rely_Tag, auto Member, typename Target, typename Source> requires
detail::Rely_Tag_In<Rely_Tag, typename Object::Relies> && detail::Rely_Object<Target> &&
detail::State_Rely_Source<Source, Member> && detail::Bound_Rely_Object<Target> &&
std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>
Builder& add_dependency(Target* target, Source* source) {
return edit_rely<Rely_Tag>(
[&](auto& editor) {
@@ -288,12 +308,10 @@ struct Root::Builder {
}
);
}
template <
detail::Rely_Tag_In<typename Object::Relies> Rely_Tag,
typename Buffer_Tag,
detail::Rely_Object Target,
detail::Buffer_Rely_Source<Buffer_Tag> Source> requires
(detail::Bound_Rely_Object<Target> && std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>)
template <typename Rely_Tag, typename Buffer_Tag, typename Target, typename Source> requires
detail::Rely_Tag_In<Rely_Tag, typename Object::Relies> && detail::Rely_Object<Target> &&
detail::Buffer_Rely_Source<Source, Buffer_Tag> && detail::Bound_Rely_Object<Target> &&
std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>
Builder& add_dependency(Target* target, Source* source) {
return edit_rely<Rely_Tag>(
[&](auto& editor) {
@@ -301,12 +319,10 @@ struct Root::Builder {
}
);
}
template <
detail::Rely_Tag_In<typename Object::Relies> Rely_Tag,
typename Source_Tag,
Root_Derived Target,
Root_Derived Source> requires
(detail::Bound_Rely_Object<Target> && std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>)
template <typename Rely_Tag, typename Source_Tag, typename Target, typename Source> requires
detail::Rely_Tag_In<Rely_Tag, typename Object::Relies> && Root_Derived<Target> && Root_Derived<Source> &&
detail::Bound_Rely_Object<Target> &&
std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>
Builder& add_dirty_dependency(Target* target, Source* source) {
return edit_rely<Rely_Tag>(
[&](auto& editor) {
@@ -314,12 +330,10 @@ struct Root::Builder {
}
);
}
template <
detail::Rely_Tag_In<typename Object::Relies> Rely_Tag,
auto Member,
detail::Rely_Object Target,
detail::State_Rely_Source<Member> Source> requires
(detail::Bound_Rely_Object<Target> && std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>)
template <typename Rely_Tag, auto Member, typename Target, typename Source> requires
detail::Rely_Tag_In<Rely_Tag, typename Object::Relies> && detail::Rely_Object<Target> &&
detail::State_Rely_Source<Source, Member> && detail::Bound_Rely_Object<Target> &&
std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>
Builder& remove_dependency(Target* target, Source* source) {
return edit_rely<Rely_Tag>(
[&](auto& editor) {
@@ -327,12 +341,10 @@ struct Root::Builder {
}
);
}
template <
detail::Rely_Tag_In<typename Object::Relies> Rely_Tag,
typename Buffer_Tag,
detail::Rely_Object Target,
detail::Buffer_Rely_Source<Buffer_Tag> Source> requires
(detail::Bound_Rely_Object<Target> && std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>)
template <typename Rely_Tag, typename Buffer_Tag, typename Target, typename Source> requires
detail::Rely_Tag_In<Rely_Tag, typename Object::Relies> && detail::Rely_Object<Target> &&
detail::Buffer_Rely_Source<Source, Buffer_Tag> && detail::Bound_Rely_Object<Target> &&
std::derived_from<Target, detail::Rely_Bound_Object<Rely_Tag, typename Object::Relies>>
Builder& remove_dependency(Target* target, Source* source) {
return edit_rely<Rely_Tag>(
[&](auto& editor) {
+1 -1
View File
@@ -1,5 +1,5 @@
#pragma once
#include "base/object.hpp"
#include "double_buffer/object.hpp"
#include <array>
#include <string>
#include <taskflow/taskflow.hpp>
+45
View File
@@ -0,0 +1,45 @@
#include "double_buffer/core.hpp"
#include <gtest/gtest.h>
namespace {
struct Value {
int first{};
int second{};
bool operator==(const Value&) const = default;
};
}
TEST(double_buffer, exchange_only_swaps_sides) {
double_buffer::Double_Buffer<Value> 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<Value, double_buffer::Buffer_Direction::inward> 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<Value, double_buffer::Buffer_Direction::outward> 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);
}
+4 -3
View File
@@ -1,4 +1,5 @@
#include "../kernel/render.hpp"
int main() {
return 0;
#include <gtest/gtest.h>
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+95
View File
@@ -0,0 +1,95 @@
#include "double_buffer/object.hpp"
#include <gtest/gtest.h>
namespace {
struct Object_State_Tag {};
struct Test_Object : double_buffer::Impl<Test_Object, double_buffer::Root, double_buffer::State_Type<Object_State_Tag>> {
struct Prop : Prev_Prop {
int first{};
int second{};
};
struct State : Prev_State<Object_State_Tag> {
int first{};
int second{};
bool operator==(const State&) const = default;
};
struct Private : Prev_Private {};
};
using Object = double_buffer::Attach_Object<Test_Object>;
struct Derived_State_Tag {};
struct Derived_Object : double_buffer::Impl<Derived_Object, Test_Object, double_buffer::State_Type<Derived_State_Tag>> {
struct Prop : Prev_Prop {};
struct State : Prev_State<Derived_State_Tag> {
int derived{};
bool operator==(const State&) const = default;
};
struct Private : Prev_Private {};
};
using Derived = double_buffer::Attach_Object<Derived_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<Object_State_Tag>(
[&](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<Object_State_Tag>();
EXPECT_EQ(calls, 1);
}
static_assert(requires(Object& object) {
object.template access_state<Object_State_Tag>([](const auto&) {});
});
struct Missing_State_Tag {};
static_assert(!double_buffer::detail::State_Tag_In<Missing_State_Tag, Object::States>);
TEST(state_tag, inherited_tags_remain_independently_addressable) {
Derived object;
int base_calls = 0;
int derived_calls = 0;
object.set_state_callback<Object_State_Tag>([&](const auto&) { ++base_calls; });
object.set_state_callback<Derived_State_Tag>([&](const auto&) { ++derived_calls; });
object.notify_state<Object_State_Tag>();
EXPECT_EQ(base_calls, 1);
EXPECT_EQ(derived_calls, 0);
object.notify_state<Derived_State_Tag>();
EXPECT_EQ(base_calls, 1);
EXPECT_EQ(derived_calls, 1);
}
static_assert(double_buffer::detail::State_Tag_In<double_buffer::Root_State_Tag, Derived::States>);
static_assert(double_buffer::detail::State_Tag_In<Object_State_Tag, Derived::States>);
static_assert(double_buffer::detail::State_Tag_In<Derived_State_Tag, Derived::States>);
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);
}
+111
View File
@@ -0,0 +1,111 @@
#include "double_buffer/object.hpp"
#include <gtest/gtest.h>
namespace {
struct Node_State_Tag {};
struct Graph_State_Tag {};
struct Graph_Tag {};
struct Node_Object : double_buffer::Impl<Node_Object, double_buffer::Root, double_buffer::State_Type<Node_State_Tag>> {
struct Prop : Prev_Prop {};
struct State : Prev_State<Node_State_Tag> {
int value{};
bool operator==(const State&) const = default;
};
struct Private : Prev_Private {};
};
using Node = double_buffer::Attach_Object<Node_Object>;
struct Graph_Object : double_buffer::Impl<Graph_Object, double_buffer::Root, double_buffer::State_Type<Graph_State_Tag>, double_buffer::Rely_Type<Graph_Tag, Node_Object>> {
struct Prop : Prev_Prop {};
struct State : Prev_State<Graph_State_Tag> {
bool operator==(const State&) const = default;
};
struct Private : Prev_Private {};
};
using Graph = double_buffer::Attach_Object<Graph_Object>;
}
TEST(rely_storage, edited_graph_moves_inward_and_stays_synchronized) {
Node first;
Node second;
Graph graph;
auto result = graph.edit_rely<Graph_Tag>(
[&](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<Graph_Tag>();
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<Graph_Tag>([&](auto& editor) { editor.add(&node); }).has_value());
graph.exchange();
auto& buffer = graph.d.rely_storage.get<Graph_Tag>();
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<Graph_Tag>(
[&](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<Graph_Tag>();
std::vector<double_buffer::Root*> 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<Graph_Tag>());
}
TEST(rely, cycle_is_rejected_before_pending_graph_commit) {
Node first;
Node second;
Graph graph;
auto result = graph.edit_rely<Graph_Tag>(
[&](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<Graph_Tag>().empty());
}
TEST(rely_lifetime, referenced_object_can_be_destroyed_before_graph_owner) {
Graph graph;
auto node = std::make_unique<Node>();
ASSERT_TRUE(graph.edit_rely<Graph_Tag>([&](auto& editor) { editor.add(node.get()); }).has_value());
graph.exchange();
ASSERT_EQ(graph.current_rely<Graph_Tag>().size(), 1u);
node.reset();
EXPECT_TRUE(graph.current_rely<Graph_Tag>().empty());
EXPECT_TRUE(graph.rely<Graph_Tag>().empty());
}
+172
View File
@@ -0,0 +1,172 @@
#include "render.hpp"
#include <gtest/gtest.h>
namespace {
struct Direct_State_Tag {};
struct Graph_State_Tag {};
struct Direct_Renderable : double_buffer::Impl<Direct_Renderable, aethera::Renderable, double_buffer::State_Type<Direct_State_Tag>> {
struct Prop : Prev_Prop {};
struct State : Prev_State<Direct_State_Tag> {
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<Graph_Renderable, aethera::Renderable, double_buffer::State_Type<Graph_State_Tag>> {
struct Prop : Prev_Prop {};
struct State : Prev_State<Graph_State_Tag> {
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<Direct_Renderable>;
using Graph = double_buffer::Attach_Object<Graph_Renderable>;
using Scene = double_buffer::Attach_Object<aethera::Scene>;
struct Dependency_State_Tag {};
struct Dependency_Renderable : double_buffer::Impl<Dependency_Renderable, aethera::Renderable, double_buffer::State_Type<Dependency_State_Tag>> {
struct Prop : Prev_Prop {};
struct State : Prev_State<Dependency_State_Tag> {
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<Dependency_Renderable>;
template <typename Renderable>
void add_renderable(Scene& scene, Renderable* renderable) {
ASSERT_TRUE((scene.edit_rely<aethera::Prepare_Data_Tag, aethera::Paint_Tag>(
[&](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<Direct_Renderable::Private&>(renderable.d);
auto& base = static_cast<aethera::Renderable::Private&>(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<Graph_Renderable::Private&>(renderable.d);
auto& base = static_cast<aethera::Renderable::Private&>(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<aethera::Prepare_Data_Tag>();
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<aethera::Renderable_State_Tag>([&](const auto& state) {
++renderable_updates;
EXPECT_TRUE(state.prepare_executed);
EXPECT_TRUE(state.paint_executed);
});
scene.set_state_callback<aethera::Scene_State_Tag>([&](const auto& state) {
++scene_updates;
EXPECT_GT(state.taskflow_task_count, 0u);
});
aethera::set_runtime_state_callback<aethera::Task_Runtime_State_Tag>([&](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<aethera::Task_Runtime_State_Tag>();
}
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<aethera::Prepare_Data_Tag, aethera::Paint_Tag>(
[&](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<Dependency_Renderable::Private&>(source.d);
auto& target_data = static_cast<Dependency_Renderable::Private&>(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<aethera::Prepare_Data_Tag>();
scene.process([](const auto&) {});
EXPECT_EQ(source_data.prepare_calls, 2);
EXPECT_EQ(target_data.prepare_calls, 2);
}