2D 绘图缓存节点
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "mechanism.hpp"
|
||||
namespace double_buffer {
|
||||
// Dependency_Graph 是有向无环依赖图。Node 保存对象和直接前驱,Edge 额外记录触发 dirty 的来源键,用于状态/Buffer/阶段脏标记传播。
|
||||
// Dependency_Graph 是有向无环依赖图。Node 保存对象和直接前驱;Edge 可选携带 dirty 来源键,整体对象边只参与拓扑排序。
|
||||
struct Dependency_Graph {
|
||||
using Error = Dependency_Graph_Error;
|
||||
struct Node {
|
||||
@@ -157,6 +157,11 @@ public:
|
||||
void disconnect(Root* object) {
|
||||
edit_dependency_graph->disconnect_impl(object);
|
||||
}
|
||||
/* 添加整个对象依赖;只形成拓扑边,不注册 dirty 传播。 */
|
||||
template <detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Dependency_Object Source>
|
||||
Node* add_dependency(Target* target, Source* source) {
|
||||
return edit_dependency_graph->template add_dependency_runtime<Bound_Object, Target, Source>(target, source, nullptr, nullptr, nullptr, bind_node_data);
|
||||
}
|
||||
/* 添加 Prop 字段级依赖;source 通过 set 修改该字段时目标阶段变脏。 */
|
||||
template <auto Member, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Prop_Dependency_Source<Member> Source>
|
||||
Node* add_prop_dependency(Target* target, Source* source) {
|
||||
@@ -295,6 +300,7 @@ private:
|
||||
void rebuild_dirty_edges() {
|
||||
dirty_edges.clear();
|
||||
for (const auto& edge : edges) {
|
||||
if (!edge.source_key || !edge.target_tag || !edge.target_dirty_key) continue;
|
||||
Dirty_Source source{edge.source, edge.source_key};
|
||||
auto [first, last] = dirty_edges.equal_range(source);
|
||||
auto current = std::find_if(
|
||||
|
||||
@@ -26,8 +26,6 @@ using double_buffer::Dependency_Graph;
|
||||
using double_buffer::Dependency_Graph_Error;
|
||||
/* Prepare 数据阶段在 Dependency_Graph 图中的标签。 */
|
||||
struct Prepare_Data_Tag {};
|
||||
/* Paint 阶段在 Dependency_Graph 图中的标签。 */
|
||||
struct Paint_Tag {};
|
||||
/* 全局 Taskflow 运行时状态标签,用于注册状态回调。 */
|
||||
struct Task_Runtime_State_Tag {};
|
||||
/* 单一 Taskflow 任务类型的累计统计。 */
|
||||
|
||||
@@ -71,7 +71,7 @@ struct Renderable : Def<Renderable, Root> {
|
||||
/* Renderable 每次 Scene 执行后发布的阶段状态与统计。 */
|
||||
struct State : Prev_State {
|
||||
bool prepare_dirty{}; /* Prepare 条件判断时观察到的 Prepare_Data_Tag dirty 状态。 */
|
||||
bool paint_dirty{}; /* Paint 条件判断时观察到的 Paint_Tag dirty 状态。 */
|
||||
bool paint_dirty{}; /* 专用渲染阶段条件判断时观察到的 dirty 状态。 */
|
||||
bool prepare_executed{}; /* 本次 Scene 执行是否运行了 Prepare 数据函数或子图。 */
|
||||
bool paint_executed{}; /* 本次 Scene 执行是否运行了 Paint 数据函数或子图。 */
|
||||
bool prepare_graph_rebuilt{}; /* 本次 Prepare 条件判断是否重建了 Prepare 子图。 */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "scene.hpp"
|
||||
#include "scene.hpp" /* 后端共有 Prepare Scene 实现。 */
|
||||
namespace aethera {
|
||||
Scene::Private::Private() : runtime(std::make_unique<Runtime>()) {}
|
||||
Scene::Private::~Private() {
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
namespace aethera {
|
||||
/* Scene 状态标签,用于访问和订阅 Scene::State。 */
|
||||
/*
|
||||
* Scene 汇总 Prepare/Paint 两张 Dependency_Graph 图并构建总 Taskflow。
|
||||
* Scene 只汇总跨渲染后端共有的 Prepare 数据依赖图并构建 Taskflow。
|
||||
* 用户最终通过 Impl<Scene> 创建可使用实例;编辑 Dependency_Graph 后调用 advance() 提交结构变化,再调用 process(...) 执行当前场景。
|
||||
*/
|
||||
struct Scene : Def<Scene, Root, Dependency_Graph_Type<Prepare_Data_Tag, Renderable>, Dependency_Graph_Type<Paint_Tag, Renderable>> {
|
||||
struct Scene : Def<Scene, Root, Dependency_Graph_Type<Prepare_Data_Tag, Renderable>> {
|
||||
/* Scene 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */
|
||||
struct Prop : Prev_Prop {};
|
||||
/* Scene 每次 process(...) 后发布的总图结构与执行统计。 */
|
||||
struct State : Prev_State {
|
||||
bool taskflow_rebuilt{}; /* 本次 process(...) 前的 advance 是否重新构建了总 Taskflow。 */
|
||||
std::size_t renderable_count{}; /* Prepare/Paint 两张依赖图中去重后的 Renderable 数量。 */
|
||||
std::size_t taskflow_task_count{}; /* 当前总 Taskflow 中的任务节点数量。 */
|
||||
std::size_t taskflow_dependency_count{}; /* 当前总 Taskflow 中的直接依赖边数量。 */
|
||||
std::size_t taskflow_max_predecessors{}; /* 当前总 Taskflow 中单个任务的最大直接前驱数量。 */
|
||||
std::size_t taskflow_max_successors{}; /* 当前总 Taskflow 中单个任务的最大直接后继数量。 */
|
||||
std::uint64_t taskflow_execution_time_ns{}; /* 本次 process(...) 执行总 Taskflow 的耗时,单位为纳秒;没有任务时为 0。 */
|
||||
bool taskflow_rebuilt{}; /* 本次 process(...) 前的 advance 是否重新构建了总 Taskflow。 */
|
||||
std::size_t renderable_count{}; /* Prepare 数据依赖图中的 Renderable 数量。 */
|
||||
std::size_t taskflow_task_count{}; /* 当前 Prepare Taskflow 中的任务节点数量。 */
|
||||
std::size_t taskflow_dependency_count{}; /* 当前 Prepare Taskflow 中的直接依赖边数量。 */
|
||||
std::size_t taskflow_max_predecessors{}; /* 当前 Prepare Taskflow 中单个任务的最大直接前驱数量。 */
|
||||
std::size_t taskflow_max_successors{}; /* 当前 Prepare Taskflow 中单个任务的最大直接后继数量。 */
|
||||
std::uint64_t taskflow_execution_time_ns{}; /* 本次 process(...) 执行 Prepare Taskflow 的耗时,单位为纳秒;没有任务时为 0。 */
|
||||
/* 支持测试、快照比较和变更检测的逐字段相等比较。 */
|
||||
bool operator==(const State&) const = default;
|
||||
};
|
||||
|
||||
+16
-78
@@ -5,9 +5,9 @@
|
||||
#include <vector>
|
||||
namespace aethera {
|
||||
struct Scene::Private : Prev_Private {
|
||||
struct Result {}; /* process(...) 完成回调的结果类型;当前仅表示完成。 */
|
||||
struct Runtime; /* Scene 的 Taskflow 构建产物;完整定义位于本文件下方。 */
|
||||
std::unique_ptr<Runtime> runtime; /* Scene 唯一运行时构建产物的所有权。 */
|
||||
struct Result {}; /* process(...) 完成回调的结果类型;当前仅表示完成。 */
|
||||
struct Runtime; /* Scene 的 Taskflow 构建产物;完整定义位于本文件下方。 */
|
||||
std::unique_ptr<Runtime> runtime; /* Scene 唯一运行时构建产物的所有权。 */
|
||||
Private();
|
||||
~Private();
|
||||
/* Def CRTP hook:所有缓冲推进后重建必要的总 Taskflow,并更新 Scene_State_Tag 状态层。 */
|
||||
@@ -23,7 +23,7 @@ struct Scene::Private : Prev_Private {
|
||||
/* 派生 Scene 的 Private 还可覆盖 Def::Private 的四个 State 生命周期 hook,并通过 State_Access::get<Tag>() 访问状态层。 */
|
||||
};
|
||||
struct Scene::Private::Runtime {
|
||||
std::unique_ptr<tf::Taskflow> taskflow; /* 当前已构建的总 Taskflow;为空表示尚未构建。 */
|
||||
std::unique_ptr<tf::Taskflow> taskflow; /* 当前已构建的总 Taskflow;为空表示尚未构建。 */
|
||||
};
|
||||
template <Attached Object, typename Callback>
|
||||
void Scene::Private::process(Object* object, Callback&& callback) requires std::invocable<Callback, const Result&> {
|
||||
@@ -56,41 +56,29 @@ void Scene::Private::after_advance(Object* object,
|
||||
}
|
||||
);
|
||||
bool taskflow_dirty = !runtime->taskflow;
|
||||
object->template access_pending_dependency_graph<Prepare_Data_Tag, Paint_Tag>(
|
||||
[&](auto& prepare_state, auto& paint_state) {
|
||||
taskflow_dirty = taskflow_dirty || prepare_state.dirty() || paint_state.dirty();
|
||||
}
|
||||
);
|
||||
object->template access_pending_dependency_graph<Prepare_Data_Tag>(
|
||||
[&](auto& prepare_state) { taskflow_dirty = taskflow_dirty || prepare_state.dirty(); });
|
||||
if (!taskflow_dirty) return;
|
||||
if (!runtime->taskflow) runtime->taskflow = std::make_unique<tf::Taskflow>();
|
||||
auto& taskflow = *runtime->taskflow;
|
||||
auto prepare_dependencies = object->template current_dependency_graph<Prepare_Data_Tag>();
|
||||
auto paint_dependencies = object->template current_dependency_graph<Paint_Tag>();
|
||||
std::pmr::unordered_set<Renderable*> renderables{resource};
|
||||
prepare_dependencies.for_each_bound(
|
||||
[&](Renderable* renderable, Renderable::Private&) {
|
||||
renderables.insert(renderable);
|
||||
}
|
||||
);
|
||||
paint_dependencies.for_each_bound(
|
||||
[&](Renderable* renderable, Renderable::Private&) {
|
||||
renderables.insert(renderable);
|
||||
}
|
||||
);
|
||||
scene_state.renderable_count = renderables.size();
|
||||
detail::clear_stage_observers(&taskflow);
|
||||
taskflow.clear();
|
||||
struct Stage_Tasks {
|
||||
tf::Task prepare_entry; /* Prepare 条件任务,作为该阶段依赖入口。 */
|
||||
tf::Task prepare_exit; /* Prepare 完成任务,作为该阶段依赖出口。 */
|
||||
tf::Task paint_entry; /* Paint 条件任务,作为该阶段依赖入口。 */
|
||||
tf::Task paint_exit; /* Paint 完成任务,作为该阶段依赖出口。 */
|
||||
tf::Task prepare_entry; /* Prepare 条件任务,作为该阶段依赖入口。 */
|
||||
tf::Task prepare_exit; /* Prepare 完成任务,作为该阶段依赖出口。 */
|
||||
};
|
||||
std::pmr::unordered_map<Root*, Stage_Tasks> stage_tasks{resource};
|
||||
for (auto* renderable : renderables) {
|
||||
Root* root = renderable;
|
||||
auto* data = prepare_dependencies.private_data(root);
|
||||
if (!data) data = paint_dependencies.private_data(root);
|
||||
if (!data) continue;
|
||||
auto* dispatch = data->dispatch;
|
||||
auto prepare_if = taskflow.emplace([data, dispatch, root] {
|
||||
@@ -127,59 +115,16 @@ void Scene::Private::after_advance(Object* object,
|
||||
}
|
||||
auto prepare_done = taskflow.emplace([dispatch, root] {
|
||||
auto& state = *dispatch->state.get(root);
|
||||
if (!state.prepare_executed) return;
|
||||
root->template take_dirty<Prepare_Data_Tag>();
|
||||
root->template mark_dirty<Paint_Tag>();
|
||||
if (state.prepare_executed) root->template take_dirty<Prepare_Data_Tag>();
|
||||
dispatch->state.notify(root);
|
||||
}).name("renderable.prepare.complete");
|
||||
prepare_if.precede(prepare_run, prepare_done);
|
||||
prepare_run.precede(prepare_done);
|
||||
auto paint_if = taskflow.emplace([data, dispatch, root] {
|
||||
auto& state = *dispatch->state.get(root);
|
||||
state.paint_graph_rebuilt = false;
|
||||
state.paint_execution_time_ns = 0;
|
||||
if (dispatch->paint.builder) {
|
||||
bool rebuild = dispatch->paint.rebuild_predicate(root);
|
||||
if (!data->paint_graph_built || rebuild) {
|
||||
*data->paint_graph = dispatch->paint.builder(root);
|
||||
data->paint_graph_built = true;
|
||||
state.paint_graph_rebuilt = true;
|
||||
root->template mark_dirty<Paint_Tag>();
|
||||
}
|
||||
state.paint_task_count = data->paint_graph->num_tasks();
|
||||
}
|
||||
else {
|
||||
state.paint_task_count = 1;
|
||||
}
|
||||
bool dirty = root->template dirty<Paint_Tag>();
|
||||
state.paint_dirty = dirty;
|
||||
state.paint_executed = dispatch->paint.predicate(root, dirty);
|
||||
return state.paint_executed ? 0 : 1;
|
||||
}).name("renderable.paint.condition");
|
||||
tf::Task paint_run;
|
||||
if (dispatch->paint.builder) {
|
||||
if (!data->paint_graph) data->paint_graph = std::make_unique<tf::Taskflow>();
|
||||
paint_run = taskflow.composed_of(*data->paint_graph).name("renderable.paint.graph");
|
||||
}
|
||||
else {
|
||||
paint_run = taskflow.emplace([dispatch, root] {
|
||||
dispatch->paint.run(root);
|
||||
}).name("renderable.paint.data");
|
||||
}
|
||||
auto paint_done = taskflow.emplace([dispatch, root] {
|
||||
auto& state = *dispatch->state.get(root);
|
||||
if (state.paint_executed) root->template take_dirty<Paint_Tag>();
|
||||
dispatch->state.notify(root);
|
||||
}).name("renderable.paint.complete");
|
||||
paint_if.precede(paint_run, paint_done);
|
||||
paint_run.precede(paint_done);
|
||||
prepare_done.precede(paint_if);
|
||||
detail::bind_stage_observer(&taskflow, prepare_if.hash_value(), root, data, detail::Renderable_Stage::Prepare, detail::Stage_Observer_Point::Begin);
|
||||
detail::bind_stage_observer(&taskflow, prepare_done.hash_value(), root, data, detail::Renderable_Stage::Prepare, detail::Stage_Observer_Point::End);
|
||||
detail::bind_stage_observer(&taskflow, paint_if.hash_value(), root, data, detail::Renderable_Stage::Paint, detail::Stage_Observer_Point::Begin);
|
||||
detail::bind_stage_observer(&taskflow, paint_done.hash_value(), root, data, detail::Renderable_Stage::Paint, detail::Stage_Observer_Point::End);
|
||||
stage_tasks.emplace(root, Stage_Tasks{prepare_if, prepare_done, paint_if, paint_done});
|
||||
stage_tasks.emplace(root, Stage_Tasks{prepare_if, prepare_done});
|
||||
}
|
||||
auto connect_dependencies = [&](const auto& dependency_graph, bool prepare) {
|
||||
auto connect_dependencies = [&](const auto& dependency_graph) {
|
||||
dependency_graph.for_each(
|
||||
[&](const Dependency_Graph::Node& dependency_node) {
|
||||
if (!dependency_graph.private_data(dependency_node)) return;
|
||||
@@ -195,9 +140,7 @@ void Scene::Private::after_advance(Object* object,
|
||||
if (dependency_graph.private_data(*dependency)) {
|
||||
auto source = stage_tasks.find(dependency->object);
|
||||
if (source != stage_tasks.end()) {
|
||||
auto source_task = prepare ? source->second.prepare_exit : source->second.paint_exit;
|
||||
auto target_task = prepare ? target->second.prepare_entry : target->second.paint_entry;
|
||||
source_task.precede(target_task);
|
||||
source->second.prepare_exit.precede(target->second.prepare_entry);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -206,8 +149,7 @@ void Scene::Private::after_advance(Object* object,
|
||||
}
|
||||
);
|
||||
};
|
||||
connect_dependencies(prepare_dependencies, true);
|
||||
connect_dependencies(paint_dependencies, false);
|
||||
connect_dependencies(prepare_dependencies);
|
||||
scene_state.taskflow_task_count = taskflow.num_tasks();
|
||||
scene_state.taskflow_dependency_count = 0;
|
||||
scene_state.taskflow_max_predecessors = 0;
|
||||
@@ -219,12 +161,8 @@ void Scene::Private::after_advance(Object* object,
|
||||
scene_state.taskflow_max_successors = std::max(scene_state.taskflow_max_successors, task.num_successors());
|
||||
}
|
||||
);
|
||||
object->template access_pending_dependency_graph<Prepare_Data_Tag, Paint_Tag>(
|
||||
[](auto& prepare_state, auto& paint_state) {
|
||||
if (prepare_state.dirty()) prepare_state.take_dirty();
|
||||
if (paint_state.dirty()) paint_state.take_dirty();
|
||||
}
|
||||
);
|
||||
object->template access_pending_dependency_graph<Prepare_Data_Tag>(
|
||||
[](auto& prepare_state) { if (prepare_state.dirty()) prepare_state.take_dirty(); });
|
||||
scene_state.taskflow_rebuilt = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,22 @@ TEST(dependency_graph, topological_order_and_state_dirty_propagation) {
|
||||
source->update_state<&Node_Object::State::value>(31);
|
||||
EXPECT_TRUE(target->dirty<Graph_Tag>());
|
||||
}
|
||||
TEST(dependency_graph, whole_object_dependency_only_controls_topology) {
|
||||
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_dependency(target.get(), source.get()); }).has_value());
|
||||
graph->advance();
|
||||
std::vector<double_buffer::Root*> order;
|
||||
ASSERT_TRUE(graph->current_dependency_graph<Graph_Tag>().for_each_topological_view([&](const auto&, const auto& node) { order.push_back(node.object); }).has_value());
|
||||
ASSERT_EQ(order.size(), 2u);
|
||||
EXPECT_EQ(order[0], source.get());
|
||||
EXPECT_EQ(order[1], target.get());
|
||||
source->set<&Node_Object::Prop::value>(1);
|
||||
source->update_state<&Node_Object::State::value>(1);
|
||||
source->mark_dirty<Graph_Tag>();
|
||||
EXPECT_FALSE(target->dirty<Graph_Tag>());
|
||||
}
|
||||
TEST(dependency_graph, state_dependency_granularity_is_selectable) {
|
||||
auto member_source = build_object<Node>();
|
||||
auto member_target = build_object<Node>();
|
||||
|
||||
@@ -85,12 +85,7 @@ std::unique_ptr<Object_Type> build_object() {
|
||||
}
|
||||
template <typename Renderable>
|
||||
void add_renderable(Scene& scene, Renderable* renderable) {
|
||||
ASSERT_TRUE((scene.edit_dependency_graph<aethera::Prepare_Data_Tag, aethera::Paint_Tag>(
|
||||
[&](auto& prepare, auto& paint) {
|
||||
prepare.add(renderable);
|
||||
paint.add(renderable);
|
||||
}
|
||||
).has_value()));
|
||||
ASSERT_TRUE(scene.edit_dependency_graph<aethera::Prepare_Data_Tag>([&](auto& prepare) { prepare.add(renderable); }).has_value());
|
||||
}
|
||||
}
|
||||
TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) {
|
||||
@@ -102,12 +97,12 @@ TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) {
|
||||
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(data.paint_calls, 0);
|
||||
EXPECT_EQ(base.prepare_graph, nullptr);
|
||||
EXPECT_EQ(base.paint_graph, nullptr);
|
||||
scene->process([](const auto&) {});
|
||||
EXPECT_EQ(data.prepare_calls, 1);
|
||||
EXPECT_EQ(data.paint_calls, 1);
|
||||
EXPECT_EQ(data.paint_calls, 0);
|
||||
}
|
||||
TEST(renderable_capability, graph_stage_builds_lazily_and_rebuilds_inside_condition) {
|
||||
aethera::initialize_runtime(2);
|
||||
@@ -141,7 +136,7 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie
|
||||
renderable->set_state_callback<aethera::Renderable::Base_Tag>([&](const auto& state) {
|
||||
++renderable_updates;
|
||||
EXPECT_TRUE(state.prepare_executed);
|
||||
EXPECT_TRUE(state.paint_executed);
|
||||
EXPECT_FALSE(state.paint_executed);
|
||||
});
|
||||
scene->set_state_callback<aethera::Scene::Base_Tag>([&](const auto& state) {
|
||||
++scene_updates;
|
||||
@@ -190,13 +185,11 @@ TEST(scene_condition, upstream_change_makes_downstream_run_in_same_taskflow) {
|
||||
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) {
|
||||
ASSERT_TRUE((scene->edit_dependency_graph<aethera::Prepare_Data_Tag>(
|
||||
[&](auto& prepare) {
|
||||
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&) {});
|
||||
|
||||
Reference in New Issue
Block a user