迁移完所有的轴
This commit is contained in:
@@ -292,7 +292,7 @@ private:
|
||||
return [](Node* node, Root* root) {
|
||||
auto* object = static_cast<Object*>(root);
|
||||
if constexpr (requires { object->bind_dependency_graph_object(object); }) object->bind_dependency_graph_object(object);
|
||||
node->data = static_cast<typename Bound_Object::Private*>(&object->d);
|
||||
node->data = static_cast<typename Bound_Object::Private*>(object->d);
|
||||
};
|
||||
}
|
||||
else {
|
||||
@@ -629,5 +629,6 @@ inline Root::~Root() {
|
||||
dependency_graphs.pop_back();
|
||||
const_cast<Dependency_Graph*>(dependency_graph)->detach_destroyed(this);
|
||||
}
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,10 +236,14 @@ public:
|
||||
template <typename Object>
|
||||
struct Root::Builder {
|
||||
using Prop = typename Object::Prop;
|
||||
std::unique_ptr<Object> object;
|
||||
detail::Dependency_Graph_Build_Storage<typename Object::Dependency_Graph_Types> dependency_graph_storage;
|
||||
template <typename... Args> requires std::constructible_from<Object, Args...>
|
||||
explicit Builder(Args&&... args) : object(std::make_unique<Object>(std::forward<Args>(args)...)),
|
||||
using Buffer_Build = detail::Buffer_Build_Storage<typename Object::Buffers>;
|
||||
using Dependency_Graph_Build = detail::Dependency_Graph_Build_Storage<typename Object::Dependency_Graph_Types>;
|
||||
std::unique_ptr<Object> object; /* 尚未挂接最终 Private 的对象外壳。 */
|
||||
Prop prop{}; /* build() 前暂存的发布属性写入面。 */
|
||||
Buffer_Build buffer_storage; /* build() 前暂存的普通 Buffer 初值。 */
|
||||
Dependency_Graph_Build dependency_graph_storage; /* build() 前独立编辑并验证的依赖图。 */
|
||||
template <typename... Args>
|
||||
explicit Builder(Args&&... args) : object(new Object(std::forward<Args>(args)...)),
|
||||
dependency_graph_storage(object->memory_resource()) {}
|
||||
Builder& set_pmr(Pmr pmr) {
|
||||
object->set_pmr_resource(pmr);
|
||||
@@ -253,12 +257,12 @@ struct Root::Builder {
|
||||
}
|
||||
template <typename Owner, typename Member, detail::Prop_Member_Settable<Prop, Owner, Member> Value>
|
||||
Builder& set(Member Owner::* member, Value&& value) {
|
||||
object->set(member, std::forward<Value>(value));
|
||||
prop.*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>
|
||||
Builder& set(Value&& value) {
|
||||
object->template initialize_buffer<Tag>(std::forward<Value>(value));
|
||||
buffer_storage.template set<Tag>(std::forward<Value>(value));
|
||||
return *this;
|
||||
}
|
||||
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types>... Dependency_Graph_Tags, detail::Dependency_Graph_Edit_Callback_For<typename Object::Dependency_Graph_Types, Dependency_Graph_Tags...> Callback>
|
||||
@@ -325,7 +329,15 @@ struct Root::Builder {
|
||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> build() {
|
||||
auto validate_result = validate();
|
||||
if (!validate_result) return std::unexpected(validate_result.error());
|
||||
dependency_graph_storage.commit(object->d.dependency_graph_storage);
|
||||
auto private_data = std::make_unique<typename Object::Private>(object->memory_resource());
|
||||
*private_data->current = prop;
|
||||
buffer_storage.commit(private_data->buffer_storage);
|
||||
dependency_graph_storage.commit(private_data->dependency_graph_storage);
|
||||
object->d = private_data.release();
|
||||
// d 已指向最终 Private 后,定义层可按最终 Object 类型安装公开薄壳所需的 CRTP 分派。
|
||||
if constexpr (requires { object->bind_private_crtp(object.get()); }) {
|
||||
object->bind_private_crtp(object.get());
|
||||
}
|
||||
return std::move(object);
|
||||
}
|
||||
std::expected<void, Dependency_Graph_Error> validate() const {
|
||||
|
||||
@@ -469,6 +469,32 @@ public:
|
||||
);
|
||||
}
|
||||
};
|
||||
/* Builder 在最终 Private 创建前暂存普通 Buffer 初值,并在 build() 时写入正式双缓冲。 */
|
||||
template <typename Tuple>
|
||||
struct Buffer_Build_Storage;
|
||||
template <typename... Buffers>
|
||||
struct Buffer_Build_Storage<std::tuple<Buffers...>> {
|
||||
private:
|
||||
std::tuple<typename Buffers::Value_Type...> values; /* build() 前暂存的各 Tagged_Buffer 初值。 */
|
||||
public:
|
||||
template <Buffer_Tag_In<std::tuple<Buffers...>> Tag, Buffer_Value_Settable<Tag, std::tuple<Buffers...>> Value>
|
||||
void set(Value&& value) {
|
||||
std::get<tag_index_v<Tag, std::tuple<Buffers...>>>(values) = std::forward<Value>(value);
|
||||
}
|
||||
void commit(Buffer_Storage<std::tuple<Buffers...>>& storage) const {
|
||||
[&]<std::size_t... I>(std::index_sequence<I...>) {
|
||||
(
|
||||
[&] {
|
||||
using Buffer = std::tuple_element_t<I, std::tuple<Buffers...>>;
|
||||
auto& target = storage.template get<typename Buffer::Tag_Type>();
|
||||
*target.pending = std::get<I>(values);
|
||||
*target.current = *target.pending;
|
||||
}(),
|
||||
...
|
||||
);
|
||||
}(std::index_sequence_for<Buffers...>{});
|
||||
}
|
||||
};
|
||||
}
|
||||
/*
|
||||
* State_Access 是 State 继承链的按 Tag 访问视图。
|
||||
@@ -535,6 +561,10 @@ enum class Dependency_Graph_Error {
|
||||
};
|
||||
struct Root_State_Tag {};
|
||||
struct Root {
|
||||
/* 所有实现层 Private 的公共析构基类;Root 通过该类型唯一拥有最终 Private。 */
|
||||
struct Private {
|
||||
virtual ~Private() = default;
|
||||
};
|
||||
using Buffers = std::tuple<>;
|
||||
using Dependency_Graph_Types = std::tuple<>;
|
||||
using States = std::tuple<State_Type<Root_State_Tag>>;
|
||||
@@ -548,6 +578,9 @@ private:
|
||||
detail::Pmr_Resource pmr_resource;
|
||||
std::pmr::vector<const Dependency_Graph*> dependency_graphs;
|
||||
std::pmr::vector<const void*> dirty_tags;
|
||||
protected:
|
||||
Private* d{}; /* Builder::build() 挂接的最终 Private;由 Root 析构释放。 */
|
||||
private:
|
||||
void set_pmr_resource(Pmr pmr) noexcept {
|
||||
pmr_resource.set_resource(pmr.resource());
|
||||
}
|
||||
@@ -574,6 +607,10 @@ public:
|
||||
Root() : pmr_resource(),
|
||||
dependency_graphs(&pmr_resource),
|
||||
dirty_tags(&pmr_resource) {}
|
||||
Root(const Root&) = delete;
|
||||
Root& operator=(const Root&) = delete;
|
||||
Root(Root&&) = delete;
|
||||
Root& operator=(Root&&) = delete;
|
||||
~Root();
|
||||
[[nodiscard]] std::pmr::memory_resource* memory_resource() const noexcept {
|
||||
return const_cast<detail::Pmr_Resource*>(&pmr_resource);
|
||||
@@ -605,7 +642,6 @@ public:
|
||||
}
|
||||
template <typename Object>
|
||||
struct Builder;
|
||||
struct Private {};
|
||||
};
|
||||
template <typename T>
|
||||
concept Object_Root = requires {
|
||||
@@ -629,9 +665,8 @@ concept Dependency_Object = Attached<T> && requires {
|
||||
requires Buffer_List<typename T::Buffers>;
|
||||
};
|
||||
template <typename T>
|
||||
concept Bound_Dependency_Object = Dependency_Object<T> && requires(T* object) {
|
||||
concept Bound_Dependency_Object = Dependency_Object<T> && requires {
|
||||
typename T::Private;
|
||||
object->d;
|
||||
};
|
||||
template <typename T, typename Bound>
|
||||
concept Bound_Dependency_Graph_Target = Bound_Dependency_Object<T> && std::derived_from<T, Bound>;
|
||||
|
||||
@@ -103,7 +103,7 @@ struct Def : Base {
|
||||
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));
|
||||
callback(static_cast<typename Layer::Private&>(*object->d));
|
||||
if constexpr (Reverse) walk_private<Reverse, typename Layer::Prev_Object>(object, callback);
|
||||
}
|
||||
}
|
||||
@@ -128,25 +128,32 @@ struct Impl : Obj {
|
||||
state(std::allocator_arg, Allocator{resource}),
|
||||
buffer_storage(std::allocator_arg, Allocator{resource}),
|
||||
dependency_graph_storage(std::allocator_arg, Allocator{resource}) {}
|
||||
} d;
|
||||
};
|
||||
private:
|
||||
friend struct Root::Builder<Impl>;
|
||||
template <typename... Args> requires std::constructible_from<Obj, Args...>
|
||||
explicit Impl(Args&&... args) : Obj(std::forward<Args>(args)...), d(this->memory_resource()) {
|
||||
explicit Impl(Args&&... args) : Obj(std::forward<Args>(args)...) {
|
||||
this->template bind_object_crtp<Impl>();
|
||||
}
|
||||
private:
|
||||
friend struct Root;
|
||||
mutable Lock lock;
|
||||
[[nodiscard]] Private& data() noexcept {
|
||||
return static_cast<Private&>(*this->d);
|
||||
}
|
||||
[[nodiscard]] const Private& data() const noexcept {
|
||||
return static_cast<const Private&>(*this->d);
|
||||
}
|
||||
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);
|
||||
callback(static_cast<typename Layer::Private&>(d));
|
||||
callback(static_cast<typename Layer::Private&>(data()));
|
||||
if constexpr (Reverse) walk_private<Reverse, typename Layer::Prev_Object>(callback);
|
||||
}
|
||||
}
|
||||
template <typename Owner, typename Member>
|
||||
void before_state_set(Member Owner::* member) {
|
||||
State_Access pending_states{*d.state.pending};
|
||||
State_Access pending_states{*data().state.pending};
|
||||
auto callback = [&](auto& private_data) {
|
||||
private_data.before_state_set(this, member, pending_states);
|
||||
};
|
||||
@@ -154,35 +161,35 @@ private:
|
||||
}
|
||||
template <typename Owner, typename Member>
|
||||
void after_state_set(Member Owner::* member) {
|
||||
State_Access pending_states{*d.state.pending};
|
||||
State_Access pending_states{*data().state.pending};
|
||||
auto callback = [&](auto& private_data) {
|
||||
private_data.after_state_set(this, member, pending_states);
|
||||
};
|
||||
walk_private<true, Obj>(callback);
|
||||
}
|
||||
void before_advance() {
|
||||
State_Access pending_states{*d.state.pending};
|
||||
State_Access current_states{std::as_const(*d.state.current)};
|
||||
State_Access pending_states{*data().state.pending};
|
||||
State_Access current_states{std::as_const(*data().state.current)};
|
||||
auto callback = [&](auto& private_data) {
|
||||
private_data.before_advance(
|
||||
this,
|
||||
d.pending,
|
||||
data().pending,
|
||||
pending_states,
|
||||
d.current,
|
||||
data().current,
|
||||
current_states
|
||||
);
|
||||
};
|
||||
walk_private<false, Obj>(callback);
|
||||
}
|
||||
void after_advance() {
|
||||
State_Access pending_states{*d.state.pending};
|
||||
State_Access current_states{*d.state.current};
|
||||
State_Access pending_states{*data().state.pending};
|
||||
State_Access current_states{*data().state.current};
|
||||
auto callback = [&](auto& private_data) {
|
||||
private_data.after_advance(
|
||||
this,
|
||||
d.pending,
|
||||
data().pending,
|
||||
pending_states,
|
||||
d.current,
|
||||
data().current,
|
||||
current_states
|
||||
);
|
||||
};
|
||||
@@ -191,54 +198,54 @@ private:
|
||||
void advance_unlocked() {
|
||||
before_advance();
|
||||
// State 从 pending 向内部 current 提交;Prop 从内部 current 向 pending 发布,两者推进后的写入侧都同步为最新基线。
|
||||
static_cast<Publish_Double_Buffer<Prop>&>(d).advance();
|
||||
d.state.advance();
|
||||
d.buffer_storage.advance();
|
||||
d.dependency_graph_storage.advance();
|
||||
static_cast<Publish_Double_Buffer<Prop>&>(data()).advance();
|
||||
data().state.advance();
|
||||
data().buffer_storage.advance();
|
||||
data().dependency_graph_storage.advance();
|
||||
after_advance();
|
||||
// after_advance 只修改已提交的 current;由状态缓冲统一刷新下一次编辑基线,具体机制不得自行同步两份状态。
|
||||
*d.state.pending = *d.state.current;
|
||||
*data().state.pending = *data().state.current;
|
||||
}
|
||||
public:
|
||||
template <detail::Buffer_Tag_In<Buffers> Tag>
|
||||
auto& pending_buffer() {
|
||||
this->emit_dependency_source(detail::dependency_id<detail::Buffer_Dependency_Key<Tag>>());
|
||||
return *d.buffer_storage.template get<Tag>().pending;
|
||||
return *data().buffer_storage.template get<Tag>().pending;
|
||||
}
|
||||
template <detail::Buffer_Tag_In<Buffers> Tag>
|
||||
const auto& current_buffer() const {
|
||||
return *d.buffer_storage.template get<Tag>().current;
|
||||
return *data().buffer_storage.template get<Tag>().current;
|
||||
}
|
||||
template <detail::Buffer_Tag_In<Buffers> Tag, detail::Buffer_Value_Settable<Tag, Buffers> Value>
|
||||
void initialize_buffer(Value&& value) {
|
||||
auto& buffer = d.buffer_storage.template get<Tag>();
|
||||
auto& buffer = data().buffer_storage.template get<Tag>();
|
||||
*buffer.pending = std::forward<Value>(value);
|
||||
*buffer.current = *buffer.pending;
|
||||
}
|
||||
template <detail::Dependency_Graph_Tag_In<Dependency_Graph_Types> Tag>
|
||||
[[nodiscard]] auto pending_dependency_graph() const {
|
||||
using Bound_Object = detail::Dependency_Graph_Bound_Object<Tag, Dependency_Graph_Types>;
|
||||
const Dependency_Graph* graph = d.dependency_graph_storage.template get<Tag>().pending;
|
||||
const Dependency_Graph* graph = data().dependency_graph_storage.template get<Tag>().pending;
|
||||
return graph->typed_view<Bound_Object>();
|
||||
}
|
||||
template <detail::Dependency_Graph_Tag_In<Dependency_Graph_Types> Tag>
|
||||
[[nodiscard]] auto current_dependency_graph() const {
|
||||
using Bound_Object = detail::Dependency_Graph_Bound_Object<Tag, Dependency_Graph_Types>;
|
||||
const Dependency_Graph* graph = d.dependency_graph_storage.template get<Tag>().current;
|
||||
const Dependency_Graph* graph = data().dependency_graph_storage.template get<Tag>().current;
|
||||
return graph->typed_view<Bound_Object>();
|
||||
}
|
||||
template <detail::Dependency_Graph_Tag_In<Dependency_Graph_Types>... Tags, detail::Dependency_Graph_Access_Callback_For<Dependency_Graph_Types, Tags...> Callback>
|
||||
void access_pending_dependency_graph(Callback&& callback) {
|
||||
d.dependency_graph_storage.template access<Tags...>(std::forward<Callback>(callback));
|
||||
data().dependency_graph_storage.template access<Tags...>(std::forward<Callback>(callback));
|
||||
}
|
||||
// 依赖图只允许在编辑侧修改;回调完成后先验证 DAG,再把新结构提交到 pending。
|
||||
template <detail::Dependency_Graph_Tag_In<Dependency_Graph_Types>... Tags, detail::Dependency_Graph_Edit_Callback_For<Dependency_Graph_Types, Tags...> Callback>
|
||||
std::expected<void, Dependency_Graph_Error> edit_dependency_graph(Callback&& callback) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
return d.dependency_graph_storage.template edit<Tags...>(std::forward<Callback>(callback));
|
||||
return data().dependency_graph_storage.template edit<Tags...>(std::forward<Callback>(callback));
|
||||
}
|
||||
std::expected<void, Dependency_Graph_Error> validate_dependency_graph() const {
|
||||
return d.dependency_graph_storage.validate_pending();
|
||||
return data().dependency_graph_storage.validate_pending();
|
||||
}
|
||||
template <detail::Dependency_Graph_Tag_In<Dependency_Graph_Types> Tag, detail::Dependency_Graph_View_Node_Callback<detail::Dependency_Graph_Bound_Object<Tag, Dependency_Graph_Types>> Callback>
|
||||
std::expected<void, Dependency_Graph_Error> for_each_dependency_graph_topological(Callback&& callback) const {
|
||||
@@ -246,48 +253,60 @@ public:
|
||||
}
|
||||
template <detail::Dependency_Graph_Current_Callback Callback>
|
||||
void for_each_current_dependency_graph(Callback&& callback) const {
|
||||
d.dependency_graph_storage.for_each_current(std::forward<Callback>(callback));
|
||||
data().dependency_graph_storage.for_each_current(std::forward<Callback>(callback));
|
||||
}
|
||||
template <typename Owner, typename Member, detail::Prop_Member_Settable<Prop, Owner, Member> Value>
|
||||
Impl& set(Member Owner::* member, Value&& value) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
d.current->*member = std::forward<Value>(value);
|
||||
data().current->*member = std::forward<Value>(value);
|
||||
return *this;
|
||||
}
|
||||
template <detail::State_Tag_In<States> Tag, detail::State_Callback_For<Tag, State, States> Callback>
|
||||
void set_state_callback(Callback&& callback) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
d.state_callbacks.template set<Tag>(std::forward<Callback>(callback));
|
||||
data().state_callbacks.template set<Tag>(std::forward<Callback>(callback));
|
||||
}
|
||||
template <detail::State_Tag_In<States> Tag>
|
||||
void clear_state_callback() {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
d.state_callbacks.template clear<Tag>();
|
||||
data().state_callbacks.template clear<Tag>();
|
||||
}
|
||||
template <detail::State_Tag_In<States> Tag, detail::State_Callback_For<Tag, State, States> Callback>
|
||||
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));
|
||||
std::invoke(std::forward<Callback>(callback), static_cast<const Layer&>(*data().state.current));
|
||||
}
|
||||
template <detail::State_Tag_In<States> Tag>
|
||||
void notify_state() {
|
||||
d.state_callbacks.template notify<Tag>(*d.state.current);
|
||||
data().state_callbacks.template notify<Tag>(*data().state.current);
|
||||
}
|
||||
// State 写入 pending,随后向依赖图发出对应成员的 dirty 信号;真正进入 current 发生在 advance/process 边界。
|
||||
template <auto Member, detail::State_Member_Settable<Member, State> Value>
|
||||
void update_state(Value&& value) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
before_state_set(Member);
|
||||
d.state.pending->*Member = std::forward<Value>(value);
|
||||
data().state.pending->*Member = std::forward<Value>(value);
|
||||
after_state_set(Member);
|
||||
this->emit_dependency_source(detail::dependency_id<detail::State_Dependency_Key<Member>>());
|
||||
}
|
||||
// 在同一锁作用域内按 Tag 原子修改一组相关 State 成员,并逐项触发生命周期钩子和依赖通知。
|
||||
template <auto... Members, typename Callback> requires
|
||||
(sizeof...(Members) > 0) &&
|
||||
(detail::State_Member<Members, State> && ...) &&
|
||||
std::invocable<Callback, State_Access<State>>
|
||||
void update_state(Callback&& callback) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
(before_state_set(Members), ...);
|
||||
std::invoke(std::forward<Callback>(callback), State_Access{*data().state.pending});
|
||||
(after_state_set(Members), ...);
|
||||
(this->emit_dependency_source(detail::dependency_id<detail::State_Dependency_Key<Members>>()), ...);
|
||||
}
|
||||
template <detail::Process_Callback_For<Private, Impl> Callback>
|
||||
void process(Callback&& callback) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
advance_unlocked();
|
||||
d.process(this, std::forward<Callback>(callback));
|
||||
data().process(this, std::forward<Callback>(callback));
|
||||
}
|
||||
void advance() {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
@@ -297,7 +316,7 @@ public:
|
||||
void advance(Callback&& callback) {
|
||||
std::lock_guard<Lock> guard(lock);
|
||||
advance_unlocked();
|
||||
std::invoke(std::forward<Callback>(callback), std::as_const(*d.state.current));
|
||||
std::invoke(std::forward<Callback>(callback), std::as_const(*data().state.current));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
namespace aethera {
|
||||
enum class Event_Type : std::uint8_t {
|
||||
resize,
|
||||
show,
|
||||
hide,
|
||||
leave,
|
||||
pointer_move,
|
||||
pointer_press,
|
||||
pointer_release,
|
||||
wheel,
|
||||
key_press,
|
||||
key_release
|
||||
};
|
||||
/* 所有输入事件的公共业务基类。 */
|
||||
struct Event {
|
||||
explicit Event(Event_Type value) : type(value) {}
|
||||
virtual ~Event() = default;
|
||||
void accept() const noexcept {
|
||||
accepted = true;
|
||||
}
|
||||
[[nodiscard]] bool is_accepted() const noexcept {
|
||||
return accepted;
|
||||
}
|
||||
Event_Type type; /* 事件种类;构造后保持不变。 */
|
||||
private:
|
||||
mutable bool accepted{}; /* 处理链是否已经消费事件。 */
|
||||
};
|
||||
template <typename T>
|
||||
concept Event_Object = std::derived_from<std::remove_cvref_t<T>, Event>;
|
||||
template <typename T>
|
||||
concept Event_Point = std::default_initializable<T> && requires(T value) {
|
||||
value.x;
|
||||
value.y;
|
||||
};
|
||||
template <typename T>
|
||||
concept Event_Size = std::default_initializable<T> && requires(T value) {
|
||||
value.width;
|
||||
value.height;
|
||||
};
|
||||
enum class Mouse_Button : std::uint8_t { none, left, right, middle };
|
||||
using Mouse_Button_Mask = std::uint8_t;
|
||||
enum class Keyboard_Modifier : std::uint8_t {
|
||||
none = 0,
|
||||
control = 1 << 0,
|
||||
shift = 1 << 1,
|
||||
alt = 1 << 2,
|
||||
meta = 1 << 3
|
||||
};
|
||||
[[nodiscard]] constexpr Keyboard_Modifier operator|(Keyboard_Modifier left,
|
||||
Keyboard_Modifier right) noexcept {
|
||||
return static_cast<Keyboard_Modifier>(static_cast<std::uint8_t>(left) | static_cast<std::uint8_t>(right));
|
||||
}
|
||||
/* 不依赖具体坐标类型的指针事件查询能力。 */
|
||||
struct Pointer_Event_Capability {
|
||||
virtual ~Pointer_Event_Capability() = default;
|
||||
[[nodiscard]] virtual double position_x() const noexcept = 0;
|
||||
[[nodiscard]] virtual double position_y() const noexcept = 0;
|
||||
[[nodiscard]] virtual Mouse_Button pointer_button() const noexcept = 0;
|
||||
[[nodiscard]] virtual Keyboard_Modifier keyboard_modifiers() const noexcept = 0;
|
||||
};
|
||||
/* 不依赖具体坐标类型的滚轮增量查询能力。 */
|
||||
struct Wheel_Event_Capability {
|
||||
virtual ~Wheel_Event_Capability() = default;
|
||||
[[nodiscard]] virtual double pixel_delta_x_value() const noexcept = 0;
|
||||
[[nodiscard]] virtual double pixel_delta_y_value() const noexcept = 0;
|
||||
[[nodiscard]] virtual double angle_delta_x_value() const noexcept = 0;
|
||||
[[nodiscard]] virtual double angle_delta_y_value() const noexcept = 0;
|
||||
};
|
||||
/* 带局部位置、全局位置、按键和修饰键的指针事件。 */
|
||||
template <Event_Point Point>
|
||||
struct Basic_Pointer_Event : Event, Pointer_Event_Capability {
|
||||
explicit Basic_Pointer_Event(Event_Type value = Event_Type::pointer_move) : Event(value) {}
|
||||
Point position{}; /* 事件接收对象局部坐标。 */
|
||||
Point global_position{}; /* 全局窗口坐标。 */
|
||||
Mouse_Button button{Mouse_Button::none}; /* 本次按下或释放的按键。 */
|
||||
Mouse_Button_Mask buttons{}; /* 事件发生时保持按下的按键集合。 */
|
||||
Keyboard_Modifier modifiers{Keyboard_Modifier::none}; /* 事件发生时的键盘修饰键集合。 */
|
||||
[[nodiscard]] double position_x() const noexcept override { return static_cast<double>(position.x); }
|
||||
[[nodiscard]] double position_y() const noexcept override { return static_cast<double>(position.y); }
|
||||
[[nodiscard]] Mouse_Button pointer_button() const noexcept override { return button; }
|
||||
[[nodiscard]] Keyboard_Modifier keyboard_modifiers() const noexcept override { return modifiers; }
|
||||
};
|
||||
/* 带角度增量和像素增量的滚轮事件。 */
|
||||
template <Event_Point Point>
|
||||
struct Basic_Wheel_Event : Basic_Pointer_Event<Point>, Wheel_Event_Capability {
|
||||
Basic_Wheel_Event() : Basic_Pointer_Event<Point>(Event_Type::wheel) {}
|
||||
double angle_delta_x{}; /* 水平方向滚轮角度增量。 */
|
||||
double angle_delta_y{}; /* 垂直方向滚轮角度增量。 */
|
||||
double pixel_delta_x{}; /* 水平方向高精度像素增量。 */
|
||||
double pixel_delta_y{}; /* 垂直方向高精度像素增量。 */
|
||||
[[nodiscard]] double pixel_delta_x_value() const noexcept override { return pixel_delta_x; }
|
||||
[[nodiscard]] double pixel_delta_y_value() const noexcept override { return pixel_delta_y; }
|
||||
[[nodiscard]] double angle_delta_x_value() const noexcept override { return angle_delta_x; }
|
||||
[[nodiscard]] double angle_delta_y_value() const noexcept override { return angle_delta_y; }
|
||||
};
|
||||
/* 带旧尺寸和新尺寸的调整事件。 */
|
||||
template <Event_Size Size>
|
||||
struct Basic_Resize_Event : Event {
|
||||
Basic_Resize_Event() : Event(Event_Type::resize) {}
|
||||
Size old_size{}; /* 调整前尺寸。 */
|
||||
Size new_size{}; /* 调整后尺寸。 */
|
||||
};
|
||||
enum class Key : std::uint16_t {
|
||||
unknown,
|
||||
escape,
|
||||
enter,
|
||||
space,
|
||||
delete_key,
|
||||
backspace,
|
||||
left,
|
||||
right,
|
||||
up,
|
||||
down
|
||||
};
|
||||
/* 键盘按下或释放事件。 */
|
||||
struct Key_Event : Event {
|
||||
explicit Key_Event(Event_Type value) : Event(value) {}
|
||||
Key key{Key::unknown}; /* 标准化按键。 */
|
||||
std::uint32_t native_key{}; /* 平台原生按键编码。 */
|
||||
Keyboard_Modifier modifiers{Keyboard_Modifier::none}; /* 事件发生时的修饰键集合。 */
|
||||
bool auto_repeat{}; /* 是否由系统自动重复产生。 */
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
namespace aethera {
|
||||
void Renderable::dispatch_event(const Event& event) {
|
||||
const auto& data = static_cast<const Private&>(*d);
|
||||
if (data.event_run) data.event_run(this, event);
|
||||
}
|
||||
namespace {
|
||||
class Stage_Registry : Pinned {
|
||||
private:
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#pragma once
|
||||
#include "event.hpp"
|
||||
#include "render_common.hpp"
|
||||
namespace aethera {
|
||||
/* Renderable 对外发布的颜色缓存 Buffer 类型。 */
|
||||
/* Renderable 派生实现声明颜色缓存时使用的业务标签。 */
|
||||
struct Color_Cache {};
|
||||
/* Renderable 状态标签,用于访问和订阅 Renderable::State。 */
|
||||
struct Renderable_State_Tag {};
|
||||
struct Renderable;
|
||||
/* 最终 Private 提供 handle_event(T*, const Event&) 时具备事件处理能力。 */
|
||||
template <typename T>
|
||||
concept Event_Renderable = Attached<T> && requires(typename T::Private& private_data, T* object, const Event& event) {
|
||||
{ private_data.handle_event(object, event) } -> std::same_as<void>;
|
||||
};
|
||||
/*
|
||||
* Prepare 数据模式定制点。
|
||||
* 最终对象的 Private 提供 void prepare_data(T* object) 即满足;若同时存在 build_prepare_graph(...),子图模式优先。
|
||||
@@ -55,7 +61,7 @@ concept Renderable_Object = Attached<T> && std::derived_from<T, Renderable> && r
|
||||
* Renderable 定义 Scene 可调度对象的公共机制。
|
||||
* 用户继续派生该定义,在派生类型的 Private 中提供 Prepare/Paint 定制点,并最终使用 Impl<T> 创建可运行实例。
|
||||
*/
|
||||
struct Renderable : Def<Renderable, Root, State_Type<Renderable_State_Tag>, Tagged_Buffer<Color_Cache>> {
|
||||
struct Renderable : Def<Renderable, Root, State_Type<Renderable_State_Tag>> {
|
||||
/* Renderable 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */
|
||||
struct Prop : Prev_Prop {};
|
||||
/* Renderable 每次 Scene 执行后发布的阶段状态与统计。 */
|
||||
@@ -75,6 +81,12 @@ struct Renderable : Def<Renderable, Root, State_Type<Renderable_State_Tag>, Tagg
|
||||
};
|
||||
/* 完整声明、内部派发以及 Prepare/Paint CRTP 能力契约见 renderable.ipp 中的 Renderable::Private。 */
|
||||
struct Private;
|
||||
/* 将事件交给最终 Private 的可选 handle_event(...) 能力。 */
|
||||
void dispatch_event(const Event& event);
|
||||
protected:
|
||||
/* Builder 挂接最终 Private 后,为事件公开薄壳绑定最终对象类型。 */
|
||||
template <Attached Object>
|
||||
void bind_private_crtp(Object* object);
|
||||
private:
|
||||
/* 数据模式的内部调度入口:执行最终对象 prepare_data(...),再触发各 CRTP 层 after_prepare_data(...)。 */
|
||||
template <Prepare_Data_Renderable Object>
|
||||
|
||||
@@ -14,6 +14,7 @@ struct Renderable::Private : Prev_Private {
|
||||
using Graph_Builder = tf::Taskflow (*)(Root*);
|
||||
using State_Get = State* (*)(Root*);
|
||||
using State_Notify = void (*)(Root*);
|
||||
using Event_Run = void (*)(Root*, const Event&);
|
||||
struct Stage_Dispatch {
|
||||
Run_Predicate predicate; /* 判断该阶段本次是否执行。 */
|
||||
Rebuild_Predicate rebuild_predicate; /* 判断已有阶段子图是否重建。 */
|
||||
@@ -30,6 +31,7 @@ struct Renderable::Private : Prev_Private {
|
||||
State_Dispatch state; /* Renderable 状态访问与发布分派。 */
|
||||
};
|
||||
const Dispatch* dispatch{}; /* 绑定最终对象类型后指向其静态分派表。 */
|
||||
Event_Run event_run{}; /* 最终 Private 具备事件能力时的无虚函数入口。 */
|
||||
std::unique_ptr<tf::Taskflow> prepare_graph; /* Prepare 子图模式的当前构建产物。 */
|
||||
std::unique_ptr<tf::Taskflow> paint_graph; /* Paint 子图模式的当前构建产物。 */
|
||||
bool prepare_graph_built{}; /* Prepare 子图是否至少成功构建过一次。 */
|
||||
@@ -48,7 +50,7 @@ struct Renderable::Private : Prev_Private {
|
||||
};
|
||||
template <Prepare_Data_Renderable Object>
|
||||
void Renderable::run_prepare_data(Object* object) {
|
||||
auto& private_data = static_cast<typename Object::Private&>(object->d);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*object->d);
|
||||
private_data.prepare_data(object);
|
||||
auto callback = [&](auto& value) {
|
||||
if constexpr (requires { value.after_prepare_data(object); }) value.after_prepare_data(object);
|
||||
@@ -68,21 +70,32 @@ inline bool Renderable::Private::should_paint(Attached auto*, const State&, bool
|
||||
return dirty;
|
||||
}
|
||||
inline void Renderable::Private::after_prepare_data(Attached auto*) {}
|
||||
template <Attached Object>
|
||||
void Renderable::bind_private_crtp(Object* object) {
|
||||
auto& data = static_cast<Private&>(*object->d);
|
||||
if constexpr (Event_Renderable<Object>) {
|
||||
data.event_run = [](Root* root, const Event& event) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
private_data.handle_event(value, event);
|
||||
};
|
||||
}
|
||||
}
|
||||
inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
||||
using Object = std::remove_pointer_t<decltype(object)>;
|
||||
static_assert(Renderable_Object<Object>);
|
||||
auto& data = static_cast<Private&>(object->d);
|
||||
auto& data = static_cast<Private&>(*object->d);
|
||||
static const Private::Dispatch dispatch{
|
||||
{
|
||||
[](Root* root, bool dirty) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(value->d);
|
||||
return private_data.should_prepare(value, *value->d.state.current, dirty);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
return private_data.should_prepare(value, *private_data.state.current, dirty);
|
||||
},
|
||||
[](Root* root) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(value->d);
|
||||
return private_data.should_rebuild_prepare_graph(value, *value->d.state.current);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
return private_data.should_rebuild_prepare_graph(value, *private_data.state.current);
|
||||
},
|
||||
[]() -> Private::Stage_Run {
|
||||
if constexpr (Prepare_Data_Renderable<Object>) {
|
||||
@@ -99,8 +112,8 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
||||
if constexpr (Prepare_Graph_Renderable<Object>) {
|
||||
return [](Root* root) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(value->d);
|
||||
return private_data.build_prepare_graph(value, *value->d.state.current);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
return private_data.build_prepare_graph(value, *private_data.state.current);
|
||||
};
|
||||
}
|
||||
else {
|
||||
@@ -111,19 +124,19 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
||||
{
|
||||
[](Root* root, bool dirty) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(value->d);
|
||||
return private_data.should_paint(value, *value->d.state.current, dirty);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
return private_data.should_paint(value, *private_data.state.current, dirty);
|
||||
},
|
||||
[](Root* root) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(value->d);
|
||||
return private_data.should_rebuild_paint_graph(value, *value->d.state.current);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
return private_data.should_rebuild_paint_graph(value, *private_data.state.current);
|
||||
},
|
||||
[]() -> Private::Stage_Run {
|
||||
if constexpr (Paint_Data_Renderable<Object>) {
|
||||
return [](Root* root) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(value->d);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
private_data.paint(value);
|
||||
};
|
||||
}
|
||||
@@ -135,8 +148,8 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
||||
if constexpr (Paint_Graph_Renderable<Object>) {
|
||||
return [](Root* root) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
auto& private_data = static_cast<typename Object::Private&>(value->d);
|
||||
return private_data.build_paint_graph(value, *value->d.state.current);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
return private_data.build_paint_graph(value, *private_data.state.current);
|
||||
};
|
||||
}
|
||||
else {
|
||||
@@ -147,7 +160,8 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
||||
{
|
||||
[](Root* root) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
return static_cast<State*>(value->d.state.current);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||
return static_cast<State*>(private_data.state.current);
|
||||
},
|
||||
[](Root* root) {
|
||||
auto* value = static_cast<Object*>(root);
|
||||
|
||||
@@ -27,7 +27,8 @@ struct Scene::Private::Runtime {
|
||||
};
|
||||
template <Attached Object, typename Callback>
|
||||
void Scene::Private::process(Object* object, Callback&& callback) requires std::invocable<Callback, const Result&> {
|
||||
auto& state = static_cast<State&>(*object->d.state.current);
|
||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||
auto& state = static_cast<State&>(*private_data.state.current);
|
||||
state.taskflow_execution_time_ns = 0;
|
||||
if (runtime->taskflow && !runtime->taskflow->empty()) state.taskflow_execution_time_ns = detail::run_taskflow(*runtime->taskflow);
|
||||
object->template notify_state<Scene_State_Tag>();
|
||||
|
||||
@@ -19,60 +19,70 @@ struct Graph_Object : double_buffer::Def<Graph_Object, double_buffer::Root, doub
|
||||
bool operator==(const State&) const = default;
|
||||
};
|
||||
struct Private : Prev_Private {};
|
||||
auto& data_for_test();
|
||||
};
|
||||
using Graph = double_buffer::Impl<Graph_Object>;
|
||||
inline auto& Graph_Object::data_for_test() {
|
||||
return static_cast<Graph::Private&>(*d);
|
||||
}
|
||||
template <typename Object_Type>
|
||||
std::unique_ptr<Object_Type> build_object() {
|
||||
auto result = typename Object_Type::Builder{}.build();
|
||||
if (!result) std::terminate();
|
||||
return std::move(result).value();
|
||||
}
|
||||
static_assert(!double_buffer::detail::Dependency_Object<Node_Object>);
|
||||
static_assert(double_buffer::detail::Dependency_Object<Node>);
|
||||
static_assert(!double_buffer::detail::State_Dependency_Source<Node_Object, &Node_Object::State::value>);
|
||||
}
|
||||
TEST(dependency_graph_storage, edited_graph_commits_and_stays_synchronized) {
|
||||
Node first;
|
||||
Node second;
|
||||
Graph graph;
|
||||
auto result = graph.edit_dependency_graph<Graph_Tag>(
|
||||
auto first = build_object<Node>();
|
||||
auto second = build_object<Node>();
|
||||
auto graph = build_object<Graph>();
|
||||
auto result = graph->edit_dependency_graph<Graph_Tag>(
|
||||
[&](auto& editor) {
|
||||
editor.add(&first);
|
||||
editor.add(&second);
|
||||
editor.template add_dependency<&Node_Object::State::value>(&second, &first);
|
||||
editor.add(first.get());
|
||||
editor.add(second.get());
|
||||
editor.template add_dependency<&Node_Object::State::value>(second.get(), first.get());
|
||||
}
|
||||
);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
auto& buffer = graph.d.dependency_graph_storage.get<Graph_Tag>();
|
||||
auto& buffer = graph->data_for_test().dependency_graph_storage.get<Graph_Tag>();
|
||||
auto* pending_before = buffer.pending;
|
||||
auto* current_before = buffer.current;
|
||||
graph.advance();
|
||||
graph->advance();
|
||||
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));
|
||||
EXPECT_TRUE(buffer.current->depends_on(second.get(), first.get()));
|
||||
EXPECT_TRUE(buffer.pending->depends_on(second.get(), first.get()));
|
||||
}
|
||||
TEST(dependency_graph_storage, unchanged_graph_does_not_advance_again) {
|
||||
Node node;
|
||||
Graph graph;
|
||||
ASSERT_TRUE(graph.edit_dependency_graph<Graph_Tag>([&](auto& editor) { editor.add(&node); }).has_value());
|
||||
graph.advance();
|
||||
auto& buffer = graph.d.dependency_graph_storage.get<Graph_Tag>();
|
||||
auto node = build_object<Node>();
|
||||
auto graph = build_object<Graph>();
|
||||
ASSERT_TRUE(graph->edit_dependency_graph<Graph_Tag>([&](auto& editor) { editor.add(node.get()); }).has_value());
|
||||
graph->advance();
|
||||
auto& buffer = graph->data_for_test().dependency_graph_storage.get<Graph_Tag>();
|
||||
auto* pending = buffer.pending;
|
||||
auto* current = buffer.current;
|
||||
graph.advance();
|
||||
graph->advance();
|
||||
EXPECT_EQ(buffer.pending, pending);
|
||||
EXPECT_EQ(buffer.current, current);
|
||||
}
|
||||
TEST(dependency_graph, topological_order_and_state_dirty_propagation) {
|
||||
Node source;
|
||||
Node target;
|
||||
Graph graph;
|
||||
ASSERT_TRUE(graph.edit_dependency_graph<Graph_Tag>(
|
||||
auto source = build_object<Node>();
|
||||
auto target = build_object<Node>();
|
||||
auto graph = build_object<Graph>();
|
||||
ASSERT_TRUE(graph->edit_dependency_graph<Graph_Tag>(
|
||||
[&](auto& editor) {
|
||||
editor.add(&source);
|
||||
editor.add(&target);
|
||||
editor.template add_dependency<&Node_Object::State::value>(&target, &source);
|
||||
editor.add(source.get());
|
||||
editor.add(target.get());
|
||||
editor.template add_dependency<&Node_Object::State::value>(target.get(), source.get());
|
||||
}
|
||||
).has_value());
|
||||
graph.advance();
|
||||
auto view = graph.current_dependency_graph<Graph_Tag>();
|
||||
graph->advance();
|
||||
auto view = graph->current_dependency_graph<Graph_Tag>();
|
||||
std::vector<double_buffer::Root*> order;
|
||||
auto result = view.for_each_topological_view(
|
||||
[&](const auto&, const auto& node) {
|
||||
@@ -81,34 +91,34 @@ TEST(dependency_graph, topological_order_and_state_dirty_propagation) {
|
||||
);
|
||||
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>());
|
||||
EXPECT_EQ(order[0], source.get());
|
||||
EXPECT_EQ(order[1], target.get());
|
||||
source->update_state<&Node_Object::State::value>(31);
|
||||
EXPECT_TRUE(target->dirty<Graph_Tag>());
|
||||
}
|
||||
TEST(dependency_graph, cycle_is_rejected_before_pending_graph_commit) {
|
||||
Node first;
|
||||
Node second;
|
||||
Graph graph;
|
||||
auto result = graph.edit_dependency_graph<Graph_Tag>(
|
||||
auto first = build_object<Node>();
|
||||
auto second = build_object<Node>();
|
||||
auto graph = build_object<Graph>();
|
||||
auto result = graph->edit_dependency_graph<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);
|
||||
editor.add(first.get());
|
||||
editor.add(second.get());
|
||||
editor.template add_dependency<&Node_Object::State::value>(first.get(), second.get());
|
||||
editor.template add_dependency<&Node_Object::State::value>(second.get(), first.get());
|
||||
}
|
||||
);
|
||||
ASSERT_FALSE(result.has_value());
|
||||
EXPECT_EQ(result.error(), double_buffer::Dependency_Graph_Error::cycle);
|
||||
EXPECT_TRUE(graph.pending_dependency_graph<Graph_Tag>().empty());
|
||||
EXPECT_TRUE(graph->pending_dependency_graph<Graph_Tag>().empty());
|
||||
}
|
||||
TEST(dependency_graph_lifetime, referenced_object_can_be_destroyed_before_graph_owner) {
|
||||
Graph graph;
|
||||
auto node = std::make_unique<Node>();
|
||||
ASSERT_TRUE(graph.edit_dependency_graph<Graph_Tag>([&](auto& editor) { editor.add(node.get()); }).has_value());
|
||||
graph.advance();
|
||||
ASSERT_EQ(graph.current_dependency_graph<Graph_Tag>().size(), 1u);
|
||||
auto graph = build_object<Graph>();
|
||||
auto node = build_object<Node>();
|
||||
ASSERT_TRUE(graph->edit_dependency_graph<Graph_Tag>([&](auto& editor) { editor.add(node.get()); }).has_value());
|
||||
graph->advance();
|
||||
ASSERT_EQ(graph->current_dependency_graph<Graph_Tag>().size(), 1u);
|
||||
node.reset();
|
||||
EXPECT_TRUE(graph.current_dependency_graph<Graph_Tag>().empty());
|
||||
EXPECT_TRUE(graph.pending_dependency_graph<Graph_Tag>().empty());
|
||||
EXPECT_TRUE(graph->current_dependency_graph<Graph_Tag>().empty());
|
||||
EXPECT_TRUE(graph->pending_dependency_graph<Graph_Tag>().empty());
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
namespace {
|
||||
struct Object_State_Tag {};
|
||||
struct Test_Object : double_buffer::Def<Test_Object, double_buffer::Root, double_buffer::State_Type<Object_State_Tag>> {
|
||||
struct Object_Buffer_Tag {};
|
||||
struct Test_Object : double_buffer::Def<Test_Object, double_buffer::Root, double_buffer::State_Type<Object_State_Tag>, double_buffer::Tagged_Buffer<Object_Buffer_Tag, int>> {
|
||||
struct Prop : Prev_Prop {
|
||||
int first{};
|
||||
int second{};
|
||||
@@ -13,8 +14,12 @@ struct Test_Object : double_buffer::Def<Test_Object, double_buffer::Root, double
|
||||
bool operator==(const State&) const = default;
|
||||
};
|
||||
struct Private : Prev_Private {};
|
||||
auto& data_for_test();
|
||||
};
|
||||
using Object = double_buffer::Impl<Test_Object>;
|
||||
inline auto& Test_Object::data_for_test() {
|
||||
return static_cast<Object::Private&>(*d);
|
||||
}
|
||||
struct Derived_State_Tag {};
|
||||
struct Derived_Object : double_buffer::Def<Derived_Object, Test_Object, double_buffer::State_Type<Derived_State_Tag>> {
|
||||
struct Prop : Prev_Prop {};
|
||||
@@ -25,44 +30,84 @@ struct Derived_Object : double_buffer::Def<Derived_Object, Test_Object, double_b
|
||||
struct Private : Prev_Private {};
|
||||
};
|
||||
using Derived = double_buffer::Impl<Derived_Object>;
|
||||
struct Lifetime_State_Tag {};
|
||||
struct Lifetime_Object : double_buffer::Def<Lifetime_Object, double_buffer::Root, double_buffer::State_Type<Lifetime_State_Tag>> {
|
||||
struct Prop : Prev_Prop {};
|
||||
struct State : Prev_State<Lifetime_State_Tag> {
|
||||
bool operator==(const State&) const = default;
|
||||
};
|
||||
struct Private : Prev_Private {
|
||||
inline static int destruction_count{}; /* 测试最终 Private 是否经公共虚析构链释放。 */
|
||||
~Private() override {
|
||||
++destruction_count;
|
||||
}
|
||||
};
|
||||
};
|
||||
using Lifetime = double_buffer::Impl<Lifetime_Object>;
|
||||
template <typename Object_Type>
|
||||
std::unique_ptr<Object_Type> build_object() {
|
||||
auto result = typename Object_Type::Builder{}.build();
|
||||
if (!result) std::terminate();
|
||||
return std::move(result).value();
|
||||
}
|
||||
static_assert(!std::constructible_from<Object>);
|
||||
}
|
||||
TEST(object_lifetime, root_destroys_attached_private_through_virtual_base) {
|
||||
Lifetime_Object::Private::destruction_count = 0;
|
||||
{
|
||||
auto object = build_object<Lifetime>();
|
||||
EXPECT_EQ(Lifetime_Object::Private::destruction_count, 0);
|
||||
}
|
||||
EXPECT_EQ(Lifetime_Object::Private::destruction_count, 1);
|
||||
}
|
||||
TEST(object_builder, build_attaches_private_and_commits_staged_values) {
|
||||
Object::Builder builder;
|
||||
builder.set(&Test_Object::Prop::first, 29).set<Object_Buffer_Tag>(31);
|
||||
auto result = builder.build();
|
||||
ASSERT_TRUE(result.has_value());
|
||||
auto object = std::move(result).value();
|
||||
EXPECT_EQ(object->data_for_test().current->first, 29);
|
||||
EXPECT_EQ(object->data_for_test().pending->first, 0);
|
||||
EXPECT_EQ(object->current_buffer<Object_Buffer_Tag>(), 31);
|
||||
EXPECT_EQ(object->pending_buffer<Object_Buffer_Tag>(), 31);
|
||||
}
|
||||
TEST(object_buffer, state_commits_and_keeps_incremental_baseline) {
|
||||
Object object;
|
||||
object.update_state<&Test_Object::State::first>(3);
|
||||
object.advance();
|
||||
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.advance();
|
||||
EXPECT_EQ(object.d.state.current->first, 3);
|
||||
EXPECT_EQ(object.d.state.current->second, 5);
|
||||
auto object = build_object<Object>();
|
||||
object->update_state<&Test_Object::State::first>(3);
|
||||
object->advance();
|
||||
EXPECT_EQ(object->data_for_test().state.current->first, 3);
|
||||
EXPECT_EQ(object->data_for_test().state.pending->first, 3);
|
||||
object->update_state<&Test_Object::State::second>(5);
|
||||
object->advance();
|
||||
EXPECT_EQ(object->data_for_test().state.current->first, 3);
|
||||
EXPECT_EQ(object->data_for_test().state.current->second, 5);
|
||||
}
|
||||
TEST(object_buffer, prop_publishes_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.advance();
|
||||
EXPECT_EQ(object.d.pending->first, 17);
|
||||
EXPECT_EQ(object.d.current->first, 17);
|
||||
object.set(&Test_Object::Prop::second, 19);
|
||||
object.advance();
|
||||
EXPECT_EQ(object.d.pending->first, 17);
|
||||
EXPECT_EQ(object.d.pending->second, 19);
|
||||
auto object = build_object<Object>();
|
||||
object->set(&Test_Object::Prop::first, 17);
|
||||
EXPECT_EQ(object->data_for_test().current->first, 17);
|
||||
EXPECT_EQ(object->data_for_test().pending->first, 0);
|
||||
object->advance();
|
||||
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
||||
EXPECT_EQ(object->data_for_test().current->first, 17);
|
||||
object->set(&Test_Object::Prop::second, 19);
|
||||
object->advance();
|
||||
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
||||
EXPECT_EQ(object->data_for_test().pending->second, 19);
|
||||
}
|
||||
TEST(state_tag, callback_publishes_only_requested_layer) {
|
||||
Object object;
|
||||
auto object = build_object<Object>();
|
||||
int calls = 0;
|
||||
object.set_state_callback<Object_State_Tag>(
|
||||
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.advance();
|
||||
object->update_state<&Test_Object::State::first>(23);
|
||||
object->advance();
|
||||
EXPECT_EQ(calls, 0);
|
||||
object.notify_state<Object_State_Tag>();
|
||||
object->notify_state<Object_State_Tag>();
|
||||
EXPECT_EQ(calls, 1);
|
||||
}
|
||||
static_assert(requires(Object& object) {
|
||||
@@ -71,15 +116,15 @@ static_assert(requires(Object& object) {
|
||||
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;
|
||||
auto object = build_object<Derived>();
|
||||
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>();
|
||||
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>();
|
||||
object->notify_state<Derived_State_Tag>();
|
||||
EXPECT_EQ(base_calls, 1);
|
||||
EXPECT_EQ(derived_calls, 1);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ struct Direct_Renderable : double_buffer::Def<Direct_Renderable, aethera::Render
|
||||
++paint_calls;
|
||||
}
|
||||
};
|
||||
Private& data_for_test();
|
||||
};
|
||||
struct Graph_Renderable : double_buffer::Def<Graph_Renderable, aethera::Renderable, double_buffer::State_Type<Graph_State_Tag>> {
|
||||
struct Prop : Prev_Prop {};
|
||||
@@ -42,10 +43,17 @@ struct Graph_Renderable : double_buffer::Def<Graph_Renderable, aethera::Renderab
|
||||
++paint_calls;
|
||||
}
|
||||
};
|
||||
Private& data_for_test();
|
||||
};
|
||||
using Direct = double_buffer::Impl<Direct_Renderable>;
|
||||
using Graph = double_buffer::Impl<Graph_Renderable>;
|
||||
using Scene = double_buffer::Impl<aethera::Scene>;
|
||||
inline Direct_Renderable::Private& Direct_Renderable::data_for_test() {
|
||||
return static_cast<Private&>(*d);
|
||||
}
|
||||
inline Graph_Renderable::Private& Graph_Renderable::data_for_test() {
|
||||
return static_cast<Private&>(*d);
|
||||
}
|
||||
struct Dependency_State_Tag {};
|
||||
struct Dependency_Renderable : double_buffer::Def<Dependency_Renderable, aethera::Renderable, double_buffer::State_Type<Dependency_State_Tag>> {
|
||||
struct Prop : Prev_Prop {};
|
||||
@@ -59,14 +67,25 @@ struct Dependency_Renderable : double_buffer::Def<Dependency_Renderable, aethera
|
||||
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);
|
||||
auto& private_data = static_cast<typename std::remove_pointer_t<decltype(object)>::Private&>(*this);
|
||||
if (std::exchange(publish_revision, false)) object->template update_state<&State::revision>(private_data.state.pending->revision + 1);
|
||||
}
|
||||
void paint(double_buffer::Attached auto*) {
|
||||
++paint_calls;
|
||||
}
|
||||
};
|
||||
Private& data_for_test();
|
||||
};
|
||||
using Dependency = double_buffer::Impl<Dependency_Renderable>;
|
||||
inline Dependency_Renderable::Private& Dependency_Renderable::data_for_test() {
|
||||
return static_cast<Private&>(*d);
|
||||
}
|
||||
template <typename Object_Type>
|
||||
std::unique_ptr<Object_Type> build_object() {
|
||||
auto result = typename Object_Type::Builder{}.build();
|
||||
if (!result) std::terminate();
|
||||
return std::move(result).value();
|
||||
}
|
||||
template <typename Renderable>
|
||||
void add_renderable(Scene& scene, Renderable* renderable) {
|
||||
ASSERT_TRUE((scene.edit_dependency_graph<aethera::Prepare_Data_Tag, aethera::Paint_Tag>(
|
||||
@@ -79,55 +98,55 @@ void add_renderable(Scene& scene, Renderable* renderable) {
|
||||
}
|
||||
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);
|
||||
auto renderable = build_object<Direct>();
|
||||
auto scene = build_object<Scene>();
|
||||
add_renderable(*scene, renderable.get());
|
||||
scene->process([](const auto&) {});
|
||||
auto& data = renderable->data_for_test();
|
||||
auto& base = static_cast<aethera::Renderable::Private&>(data);
|
||||
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&) {});
|
||||
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);
|
||||
auto renderable = build_object<Graph>();
|
||||
auto scene = build_object<Scene>();
|
||||
add_renderable(*scene, renderable.get());
|
||||
auto& data = renderable->data_for_test();
|
||||
auto& base = static_cast<aethera::Renderable::Private&>(data);
|
||||
EXPECT_EQ(base.prepare_graph, nullptr);
|
||||
scene.process([](const auto&) {});
|
||||
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&) {});
|
||||
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&) {});
|
||||
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);
|
||||
auto renderable = build_object<Direct>();
|
||||
auto scene = build_object<Scene>();
|
||||
add_renderable(*scene, renderable.get());
|
||||
int renderable_updates = 0;
|
||||
int scene_updates = 0;
|
||||
int runtime_updates = 0;
|
||||
renderable.set_state_callback<aethera::Renderable_State_Tag>([&](const auto& state) {
|
||||
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->set_state_callback<aethera::Scene_State_Tag>([&](const auto& state) {
|
||||
++scene_updates;
|
||||
EXPECT_GT(state.taskflow_task_count, 0u);
|
||||
});
|
||||
@@ -136,7 +155,7 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie
|
||||
EXPECT_GE(state.completed_taskflow_count, 1u);
|
||||
EXPECT_GT(state.observed_task_count, 0u);
|
||||
});
|
||||
scene.process([](const auto&) {});
|
||||
scene->process([](const auto&) {});
|
||||
EXPECT_EQ(renderable_updates, 1);
|
||||
EXPECT_EQ(scene_updates, 1);
|
||||
EXPECT_EQ(runtime_updates, 1);
|
||||
@@ -144,13 +163,13 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie
|
||||
}
|
||||
TEST(scene_state, structural_statistics_survive_a_process_without_rebuild) {
|
||||
aethera::initialize_runtime(2);
|
||||
Direct renderable;
|
||||
Scene scene;
|
||||
add_renderable(scene, &renderable);
|
||||
auto renderable = build_object<Direct>();
|
||||
auto scene = build_object<Scene>();
|
||||
add_renderable(*scene, renderable.get());
|
||||
std::size_t task_count{};
|
||||
std::size_t dependency_count{};
|
||||
int updates{};
|
||||
scene.set_state_callback<aethera::Scene_State_Tag>([&](const auto& state) {
|
||||
scene->set_state_callback<aethera::Scene_State_Tag>([&](const auto& state) {
|
||||
EXPECT_EQ(state.renderable_count, 1u);
|
||||
EXPECT_GT(state.taskflow_task_count, 0u);
|
||||
if (updates == 0) {
|
||||
@@ -165,35 +184,35 @@ TEST(scene_state, structural_statistics_survive_a_process_without_rebuild) {
|
||||
}
|
||||
++updates;
|
||||
});
|
||||
scene.process([](const auto&) {});
|
||||
scene.process([](const auto&) {});
|
||||
scene->process([](const auto&) {});
|
||||
scene->process([](const auto&) {});
|
||||
EXPECT_EQ(updates, 2);
|
||||
}
|
||||
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_dependency_graph<aethera::Prepare_Data_Tag, aethera::Paint_Tag>(
|
||||
auto source = build_object<Dependency>();
|
||||
auto target = build_object<Dependency>();
|
||||
auto scene = build_object<Scene>();
|
||||
ASSERT_TRUE((scene->edit_dependency_graph<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);
|
||||
prepare.add(source.get());
|
||||
prepare.add(target.get());
|
||||
prepare.template add_dependency<&Dependency_Renderable::State::revision>(target.get(), source.get());
|
||||
paint.add(source.get());
|
||||
paint.add(target.get());
|
||||
}
|
||||
).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);
|
||||
scene->process([](const auto&) {});
|
||||
auto& source_data = source->data_for_test();
|
||||
auto& target_data = target->data_for_test();
|
||||
EXPECT_EQ(source_data.prepare_calls, 1);
|
||||
EXPECT_EQ(target_data.prepare_calls, 1);
|
||||
scene.process([](const auto&) {});
|
||||
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&) {});
|
||||
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