编译过
This commit is contained in:
+13
-43
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
-59
@@ -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 两份图;编辑只落到 pending,exchange 后 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,5 +1,5 @@
|
||||
#pragma once
|
||||
#include "base/object.hpp"
|
||||
#include "double_buffer/object.hpp"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <taskflow/taskflow.hpp>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user