编译过

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
+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);
}