迁移完所有的轴

This commit is contained in:
2026-08-20 20:08:32 +08:00
parent 7849dd9c19
commit 138dc37f7a
32 changed files with 1967 additions and 193 deletions
@@ -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 {
+38 -3
View File
@@ -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>;
+56 -37
View File
@@ -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));
}
};
}
+127
View File
@@ -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{}; /* 是否由系统自动重复产生。 */
};
}
+4
View File
@@ -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:
+14 -2
View File
@@ -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>
+30 -16
View File
@@ -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);
+2 -1
View File
@@ -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>();
+56 -46
View File
@@ -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());
}
+76 -31
View File
@@ -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);
}
+63 -44
View File
@@ -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);
}