提升观测系统
隔离taskflow
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
#include "Task_Graph.hpp"
|
||||
#include "Task_Graph_Internal.hpp"
|
||||
#include <stdexcept>
|
||||
#include <taskflow/taskflow.hpp>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace aethera {
|
||||
struct Task_Graph::Private {
|
||||
struct Node {
|
||||
tf::Task task{}; /* 原生节点句柄;只在静态构图期间使用。 */
|
||||
std::string name{}; /* 调用方提供的业务节点名。 */
|
||||
std::string node_id{}; /* 本图内去重后的业务节点 ID。 */
|
||||
std::shared_ptr<Private> child{}; /* 模块引用的子图;普通节点为空。 */
|
||||
};
|
||||
explicit Private(std::string graph_name)
|
||||
: taskflow(std::move(graph_name)) {}
|
||||
tf::Taskflow taskflow{}; /* 第三方 Taskflow 仅存在于本实现单元。 */
|
||||
std::vector<Node> nodes{}; /* 与原生 graph 插入顺序一致的业务节点。 */
|
||||
std::unordered_map<std::string, std::size_t> name_counts{}; /* 同名业务节点的下一个序号。 */
|
||||
std::uint64_t generation{1}; /* clear/重建后使旧 Task_Node 句柄失效。 */
|
||||
|
||||
[[nodiscard]] std::string unique_node_id(const std::string& name) {
|
||||
const std::string base = taskflow.name().empty()
|
||||
? name : taskflow.name() + "/" + name;
|
||||
const auto occurrence = name_counts[name]++;
|
||||
return occurrence == 0 ? base
|
||||
: base + "#" + std::to_string(occurrence);
|
||||
}
|
||||
void collect_nodes(std::string_view parent,
|
||||
std::vector<Taskflow_Graph_Trace::Node>& result) const {
|
||||
for (const auto& source : nodes) {
|
||||
Taskflow_Graph_Trace::Node node{};
|
||||
node.native_id = static_cast<std::uint64_t>(source.task.hash_value());
|
||||
auto local_id = std::string_view(source.node_id);
|
||||
const auto& graph_name = taskflow.name();
|
||||
if (!graph_name.empty() && local_id.starts_with(graph_name) &&
|
||||
local_id.size() > graph_name.size() &&
|
||||
local_id[graph_name.size()] == '/')
|
||||
local_id.remove_prefix(graph_name.size() + 1);
|
||||
node.node_id = parent.empty() ? source.node_id
|
||||
: std::string(parent) + "/" + std::string(local_id);
|
||||
node.parent_node_id = std::string(parent);
|
||||
node.name = source.name;
|
||||
node.type = std::string(tf::to_string(source.task.type()));
|
||||
source.task.for_each_predecessor([&](tf::Task value) {
|
||||
node.predecessors.push_back(
|
||||
static_cast<std::uint64_t>(value.hash_value()));
|
||||
});
|
||||
source.task.for_each_successor([&](tf::Task value) {
|
||||
node.successors.push_back(
|
||||
static_cast<std::uint64_t>(value.hash_value()));
|
||||
});
|
||||
const auto module_id = node.node_id;
|
||||
result.push_back(std::move(node));
|
||||
if (source.child) source.child->collect_nodes(module_id, result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Task_Node::Private {
|
||||
std::shared_ptr<Task_Graph::Private> graph{}; /* 节点所属 DAG 及其静态生命周期。 */
|
||||
std::size_t index{}; /* DAG 内节点槽位;只在实现单元解释。 */
|
||||
std::uint64_t generation{}; /* 防止 clear 后的旧句柄指向复用槽位。 */
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
void* Task_Graph_Access::native_storage(Task_Graph& graph) noexcept {
|
||||
return std::addressof(graph.d->taskflow);
|
||||
}
|
||||
|
||||
std::vector<Taskflow_Graph_Trace::Node> Task_Graph_Access::nodes(
|
||||
const Task_Graph& graph) {
|
||||
std::vector<Taskflow_Graph_Trace::Node> result;
|
||||
graph.d->collect_nodes({}, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Task_Node::Task_Node() = default;
|
||||
Task_Node::~Task_Node() = default;
|
||||
Task_Node::Task_Node(const Task_Node&) = default;
|
||||
Task_Node::Task_Node(Task_Node&&) noexcept = default;
|
||||
Task_Node& Task_Node::operator=(const Task_Node&) = default;
|
||||
Task_Node& Task_Node::operator=(Task_Node&&) noexcept = default;
|
||||
Task_Node::Task_Node(std::shared_ptr<Private> private_data)
|
||||
: d(std::move(private_data)) {}
|
||||
|
||||
void Task_Node::precede(const Task_Node& after) const {
|
||||
if (!d || !after.d || !d->graph || d->graph != after.d->graph ||
|
||||
d->generation != d->graph->generation ||
|
||||
after.d->generation != after.d->graph->generation ||
|
||||
d->index >= d->graph->nodes.size() ||
|
||||
after.d->index >= d->graph->nodes.size())
|
||||
throw std::invalid_argument(
|
||||
"Task graph dependency crosses graph ownership");
|
||||
d->graph->nodes[d->index].task.precede(
|
||||
d->graph->nodes[after.d->index].task);
|
||||
}
|
||||
|
||||
Task_Graph::Task_Graph(std::string graph_name)
|
||||
: d(std::make_shared<Private>(std::move(graph_name))) {}
|
||||
Task_Graph::~Task_Graph() = default;
|
||||
Task_Graph::Task_Graph(Task_Graph&&) noexcept = default;
|
||||
Task_Graph& Task_Graph::operator=(Task_Graph&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
if (!d) {
|
||||
d = std::move(other.d);
|
||||
return *this;
|
||||
}
|
||||
if (!other.d) {
|
||||
clear();
|
||||
return *this;
|
||||
}
|
||||
d->taskflow = std::move(other.d->taskflow);
|
||||
d->nodes = std::move(other.d->nodes);
|
||||
d->name_counts = std::move(other.d->name_counts);
|
||||
++d->generation;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Task_Node Task_Graph::add(std::string task_name,
|
||||
std::function<void()> work) {
|
||||
if (!work) throw std::invalid_argument("Task graph work is empty");
|
||||
auto task = d->taskflow.emplace(std::move(work));
|
||||
const auto node_id = d->unique_node_id(task_name);
|
||||
task.name(node_id);
|
||||
d->nodes.push_back({task, std::move(task_name), node_id, nullptr});
|
||||
return Task_Node{std::make_shared<Task_Node::Private>(
|
||||
Task_Node::Private{d, d->nodes.size() - 1, d->generation})};
|
||||
}
|
||||
|
||||
Task_Node Task_Graph::add_condition(std::string task_name,
|
||||
std::function<int()> work) {
|
||||
if (!work) throw std::invalid_argument("Task graph condition is empty");
|
||||
auto task = d->taskflow.emplace(std::move(work));
|
||||
const auto node_id = d->unique_node_id(task_name);
|
||||
task.name(node_id);
|
||||
d->nodes.push_back({task, std::move(task_name), node_id, nullptr});
|
||||
return Task_Node{std::make_shared<Task_Node::Private>(
|
||||
Task_Node::Private{d, d->nodes.size() - 1, d->generation})};
|
||||
}
|
||||
|
||||
Task_Node Task_Graph::compose(std::string task_name, Task_Graph& child) {
|
||||
auto task = d->taskflow.composed_of(child.d->taskflow);
|
||||
const auto node_id = d->unique_node_id(task_name);
|
||||
task.name(node_id);
|
||||
d->nodes.push_back({task, std::move(task_name), node_id, child.d});
|
||||
return Task_Node{std::make_shared<Task_Node::Private>(
|
||||
Task_Node::Private{d, d->nodes.size() - 1, d->generation})};
|
||||
}
|
||||
|
||||
void Task_Graph::clear() {
|
||||
d->taskflow.clear();
|
||||
d->nodes.clear();
|
||||
d->name_counts.clear();
|
||||
++d->generation;
|
||||
}
|
||||
bool Task_Graph::empty() const noexcept { return d->taskflow.empty(); }
|
||||
std::size_t Task_Graph::size() const noexcept {
|
||||
return d->taskflow.num_tasks();
|
||||
}
|
||||
const std::string& Task_Graph::name() const noexcept {
|
||||
return d->taskflow.name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
namespace aethera {
|
||||
namespace detail {
|
||||
struct Task_Graph_Access;
|
||||
}
|
||||
class Task_Graph;
|
||||
/* Task_Graph 中单个业务节点的可复制句柄,仅用于静态构图。 */
|
||||
class Task_Node {
|
||||
public:
|
||||
Task_Node();
|
||||
~Task_Node();
|
||||
Task_Node(const Task_Node&);
|
||||
Task_Node(Task_Node&&) noexcept;
|
||||
Task_Node& operator=(const Task_Node&);
|
||||
Task_Node& operator=(Task_Node&&) noexcept;
|
||||
/* 建立本节点到 after 的有向依赖;两个节点必须属于同一张图。 */
|
||||
void precede(const Task_Node& after) const;
|
||||
private:
|
||||
friend class Task_Graph;
|
||||
struct Private;
|
||||
explicit Task_Node(std::shared_ptr<Private> private_data);
|
||||
std::shared_ptr<Private> d; /* 不透明节点句柄;原生类型只在实现单元可见。 */
|
||||
};
|
||||
/*
|
||||
* Kernel 的业务 DAG。Taskflow 类型、Node 句柄和 Observer 绑定全部留在 Implementation 内。
|
||||
* 图只允许在未运行时修改;执行期间 add/clear/compose/precede 的行为不受支持。
|
||||
*/
|
||||
class Task_Graph {
|
||||
public:
|
||||
explicit Task_Graph(std::string name = {});
|
||||
~Task_Graph();
|
||||
Task_Graph(Task_Graph&&) noexcept;
|
||||
Task_Graph& operator=(Task_Graph&&) noexcept;
|
||||
Task_Graph(const Task_Graph&) = delete;
|
||||
Task_Graph& operator=(const Task_Graph&) = delete;
|
||||
/* 添加普通业务节点;name 会被规范化为帧拓扑中的唯一业务 ID。 */
|
||||
Task_Node add(std::string name, std::function<void()> work);
|
||||
/* 添加条件节点;返回的后继下标沿用 Taskflow condition 语义。 */
|
||||
Task_Node add_condition(std::string name, std::function<int()> work);
|
||||
/* 添加模块节点并引用 child;child 必须活到本图完成执行。 */
|
||||
Task_Node compose(std::string name, Task_Graph& child);
|
||||
void clear();
|
||||
[[nodiscard]] bool empty() const noexcept;
|
||||
[[nodiscard]] std::size_t size() const noexcept;
|
||||
[[nodiscard]] const std::string& name() const noexcept;
|
||||
private:
|
||||
friend struct Task_Node::Private;
|
||||
friend struct detail::Task_Graph_Access;
|
||||
struct Private;
|
||||
std::shared_ptr<Private> d; /* 唯一业务 DAG 定义的不透明所有权。 */
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "Task_Graph.hpp"
|
||||
#include "frame.hpp"
|
||||
|
||||
namespace aethera::detail {
|
||||
/* 仅供 Kernel 实现单元把不透明业务图交给原生 Executor 和帧观测器。 */
|
||||
struct Task_Graph_Access {
|
||||
[[nodiscard]] static void* native_storage(Task_Graph& graph) noexcept;
|
||||
[[nodiscard]] static std::vector<Taskflow_Graph_Trace::Node> nodes(
|
||||
const Task_Graph& graph);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include "frame.hpp"
|
||||
#include "Task_Graph.hpp"
|
||||
#include <chrono>
|
||||
#include <string_view>
|
||||
|
||||
namespace aethera::detail {
|
||||
struct Taskflow_Graph_Token {
|
||||
Render_Frame* frame{}; /* DAG 运行记录所属外部帧;帧完成前有效。 */
|
||||
std::size_t index{}; /* 帧内 graph 记录索引。 */
|
||||
};
|
||||
|
||||
struct Taskflow_Frame_Access {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
static void begin_capture(Render_Frame& frame, std::size_t workers);
|
||||
static void finish_capture(Render_Frame& frame) noexcept;
|
||||
[[nodiscard]] static bool acquire_writer(Render_Frame& frame) noexcept;
|
||||
static void release_writer(Render_Frame& frame) noexcept;
|
||||
[[nodiscard]] static bool contains_task(const Render_Frame& frame,
|
||||
std::uint64_t native_id) noexcept;
|
||||
static void append_task(Render_Frame& frame, std::size_t worker,
|
||||
std::uint64_t native_id, std::size_t queue_size,
|
||||
std::size_t queue_capacity, Clock::time_point started,
|
||||
Clock::time_point finished);
|
||||
[[nodiscard]] static Taskflow_Graph_Token begin_graph(
|
||||
Render_Frame& frame, Task_Graph& graph, std::string_view stage);
|
||||
static void finish_graph(Taskflow_Graph_Token token) noexcept;
|
||||
};
|
||||
}
|
||||
+188
-1
@@ -1,11 +1,17 @@
|
||||
#include "frame.hpp"
|
||||
#include "frame_statistics.hpp"
|
||||
#include "Taskflow_Frame_Access.hpp"
|
||||
#include "Task_Graph_Internal.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
namespace aethera {
|
||||
namespace {
|
||||
constexpr std::size_t marker_count = static_cast<std::size_t>(Frame_Trace_Marker::count);
|
||||
@@ -14,11 +20,20 @@ std::uint64_t encode_present_value(std::uint64_t value) noexcept { return value
|
||||
std::uint64_t decode_present_value(std::uint64_t value) noexcept { return value == std::numeric_limits<std::uint64_t>::max() ? value : value - 1; }
|
||||
}
|
||||
struct Render_Frame::Private {
|
||||
struct Worker_Taskflow_Trace {
|
||||
std::vector<Taskflow_Task_Trace> tasks{}; /* 仅对应 worker 写入,捕获结束后统一读取。 */
|
||||
};
|
||||
Frame_Identity identity{}; /* 外部帧管理器提供且终生不变的帧身份。 */
|
||||
std::chrono::steady_clock::time_point created_at{}; /* 所有 elapsed_ns 使用的单调时钟原点。 */
|
||||
std::uint64_t created_time_unix_ns{}; /* 用于跨进程展示的创建 Unix 时间,单位为纳秒。 */
|
||||
std::array<std::atomic_uint64_t, marker_count> markers{}; /* 每种时间点首次出现时的 elapsed_ns 加一编码。 */
|
||||
std::array<std::atomic_uint64_t, measurement_count> measurements{}; /* 每种原始耗时首次记录值的加一编码。 */
|
||||
std::atomic_bool taskflow_trace_requested{}; /* 本次逻辑帧是否请求原生 Taskflow 捕获。 */
|
||||
std::atomic_bool taskflow_trace_capturing{}; /* 原生 Observer 是否仍可写入本帧。 */
|
||||
std::atomic_size_t taskflow_trace_writers{}; /* 正在完成 on_exit 写入的 worker 数。 */
|
||||
std::vector<Worker_Taskflow_Trace> taskflow_workers{}; /* 按 Executor worker 隔离的单写者时间线。 */
|
||||
std::mutex taskflow_graph_mutex{}; /* 只在按需捕获时保护跨阶段 DAG 追加与完成标记。 */
|
||||
std::vector<Taskflow_Graph_Trace> taskflow_graphs{}; /* 本帧主动 run 的业务 DAG 元信息。 */
|
||||
};
|
||||
Render_Frame::Render_Frame(Frame_Identity identity) : d(std::make_unique<Private>()) {
|
||||
begin(identity);
|
||||
@@ -32,6 +47,14 @@ void Render_Frame::begin(Frame_Identity identity) noexcept {
|
||||
marker.store(0, std::memory_order_relaxed);
|
||||
for (auto& measurement : d->measurements)
|
||||
measurement.store(0, std::memory_order_relaxed);
|
||||
d->taskflow_trace_requested.store(false, std::memory_order_relaxed);
|
||||
d->taskflow_trace_capturing.store(false, std::memory_order_relaxed);
|
||||
d->taskflow_trace_writers.store(0, std::memory_order_relaxed);
|
||||
d->taskflow_workers.clear();
|
||||
{
|
||||
std::lock_guard lock(d->taskflow_graph_mutex);
|
||||
d->taskflow_graphs.clear();
|
||||
}
|
||||
d->markers[static_cast<std::size_t>(Frame_Trace_Marker::created)].store(encode_present_value(0), std::memory_order_relaxed);
|
||||
}
|
||||
Render_Frame::~Render_Frame() = default;
|
||||
@@ -79,6 +102,12 @@ Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) cons
|
||||
};
|
||||
|
||||
Frame_Statistics_Sample result{};
|
||||
result.set(Frame_Statistic::plot_tick_queue_ms,
|
||||
measurement(Frame_Trace_Measurement::plot_tick_queue_ns));
|
||||
result.set(Frame_Statistic::plot_update_ms,
|
||||
measurement(Frame_Trace_Measurement::plot_update_ns));
|
||||
result.set(Frame_Statistic::plot_publish_ms,
|
||||
measurement(Frame_Trace_Measurement::plot_publish_ns));
|
||||
result.set(Frame_Statistic::server_completion_ms,
|
||||
marker(Frame_Trace_Marker::frame_ready));
|
||||
result.set(Frame_Statistic::scene_render_ms, interval(
|
||||
@@ -111,8 +140,19 @@ Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) cons
|
||||
Frame_Statistic::gpu_copy_ms,
|
||||
Frame_Statistic::gpu_total_ms,
|
||||
Frame_Statistic::readback_ms};
|
||||
constexpr std::array measurement_keys{
|
||||
Frame_Trace_Measurement::backend_apply_ns,
|
||||
Frame_Trace_Measurement::backend_plan_ns,
|
||||
Frame_Trace_Measurement::backend_execute_ns,
|
||||
Frame_Trace_Measurement::backend_submit_ns,
|
||||
Frame_Trace_Measurement::gpu_fence_wait_ns,
|
||||
Frame_Trace_Measurement::gpu_render_ns,
|
||||
Frame_Trace_Measurement::gpu_transition_ns,
|
||||
Frame_Trace_Measurement::gpu_copy_ns,
|
||||
Frame_Trace_Measurement::gpu_total_ns,
|
||||
Frame_Trace_Measurement::readback_ns};
|
||||
for (std::size_t index = 0; index < measurement_statistics.size(); ++index)
|
||||
result.set(measurement_statistics[index], measurements[index]);
|
||||
result.set(measurement_statistics[index], measurement(measurement_keys[index]));
|
||||
|
||||
double remaining = marker(Frame_Trace_Marker::frame_ready);
|
||||
const auto take = [&](double requested) {
|
||||
@@ -201,4 +241,151 @@ Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) cons
|
||||
result.set(Frame_Statistic::pipeline_3d_completion_handoff_ms, remaining);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Render_Frame::request_taskflow_trace() noexcept {
|
||||
d->taskflow_trace_requested.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool Render_Frame::taskflow_trace_requested() const noexcept {
|
||||
return d->taskflow_trace_requested.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
Taskflow_Frame_Trace Render_Frame::taskflow_trace() const {
|
||||
Taskflow_Frame_Trace result{};
|
||||
result.identity = d->identity;
|
||||
result.created_time_unix_ns = d->created_time_unix_ns;
|
||||
result.worker_count = d->taskflow_workers.size();
|
||||
{
|
||||
std::lock_guard lock(d->taskflow_graph_mutex);
|
||||
result.graphs = d->taskflow_graphs;
|
||||
}
|
||||
for (const auto& worker : d->taskflow_workers)
|
||||
result.tasks.insert(result.tasks.end(), worker.tasks.begin(), worker.tasks.end());
|
||||
std::ranges::sort(result.tasks, {}, &Taskflow_Task_Trace::started_ms);
|
||||
|
||||
std::erase_if(result.tasks, [&](const Taskflow_Task_Trace& task) {
|
||||
return std::ranges::none_of(result.graphs, [&](const Taskflow_Graph_Trace& graph) {
|
||||
return std::ranges::any_of(graph.nodes, [&](const Taskflow_Graph_Trace::Node& node) {
|
||||
return node.native_id == task.native_id;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
std::unordered_map<std::uint64_t, double> finished;
|
||||
for (auto& task : result.tasks) {
|
||||
double ready{};
|
||||
bool matched_graph{};
|
||||
for (const auto& graph : result.graphs)
|
||||
for (const auto& node : graph.nodes)
|
||||
if (node.native_id == task.native_id) {
|
||||
if (!matched_graph || graph.submitted_ms < ready)
|
||||
ready = graph.submitted_ms;
|
||||
matched_graph = true;
|
||||
for (const auto predecessor : node.predecessors)
|
||||
if (const auto found = finished.find(predecessor);
|
||||
found != finished.end())
|
||||
ready = std::max(ready, found->second);
|
||||
}
|
||||
task.ready_ms = ready;
|
||||
task.queue_wait_ms = std::max(0.0, task.started_ms - ready);
|
||||
finished[task.native_id] = std::max(finished[task.native_id], task.finished_ms);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void detail::Taskflow_Frame_Access::begin_capture(Render_Frame& frame, std::size_t workers) {
|
||||
auto& data = *frame.d;
|
||||
data.taskflow_workers.clear();
|
||||
data.taskflow_workers.resize(workers);
|
||||
for (auto& worker : data.taskflow_workers) worker.tasks.reserve(64);
|
||||
{
|
||||
std::lock_guard lock(data.taskflow_graph_mutex);
|
||||
data.taskflow_graphs.clear();
|
||||
}
|
||||
data.taskflow_trace_writers.store(0, std::memory_order_relaxed);
|
||||
data.taskflow_trace_capturing.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void detail::Taskflow_Frame_Access::finish_capture(Render_Frame& frame) noexcept {
|
||||
auto& data = *frame.d;
|
||||
data.taskflow_trace_capturing.store(false, std::memory_order_release);
|
||||
while (data.taskflow_trace_writers.load(std::memory_order_acquire) != 0)
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
bool detail::Taskflow_Frame_Access::acquire_writer(Render_Frame& frame) noexcept {
|
||||
auto& data = *frame.d;
|
||||
if (!data.taskflow_trace_capturing.load(std::memory_order_acquire)) return false;
|
||||
data.taskflow_trace_writers.fetch_add(1, std::memory_order_acq_rel);
|
||||
if (data.taskflow_trace_capturing.load(std::memory_order_acquire)) return true;
|
||||
data.taskflow_trace_writers.fetch_sub(1, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
|
||||
void detail::Taskflow_Frame_Access::release_writer(Render_Frame& frame) noexcept {
|
||||
frame.d->taskflow_trace_writers.fetch_sub(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool detail::Taskflow_Frame_Access::contains_task(
|
||||
const Render_Frame& frame, std::uint64_t native_id) noexcept {
|
||||
try {
|
||||
std::lock_guard lock(frame.d->taskflow_graph_mutex);
|
||||
return std::ranges::any_of(
|
||||
frame.d->taskflow_graphs,
|
||||
[&](const Taskflow_Graph_Trace& graph) {
|
||||
return std::ranges::any_of(
|
||||
graph.nodes, [&](const Taskflow_Graph_Trace::Node& node) {
|
||||
return node.native_id == native_id;
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void detail::Taskflow_Frame_Access::append_task(
|
||||
Render_Frame& frame, std::size_t worker, std::uint64_t native_id,
|
||||
std::size_t queue_size, std::size_t queue_capacity,
|
||||
Clock::time_point started, Clock::time_point finished) {
|
||||
auto& data = *frame.d;
|
||||
if (worker >= data.taskflow_workers.size()) return;
|
||||
const auto elapsed_ms = [&](Clock::time_point value) {
|
||||
return std::chrono::duration<double, std::milli>(value - data.created_at).count();
|
||||
};
|
||||
Taskflow_Task_Trace trace{};
|
||||
trace.native_id = native_id;
|
||||
trace.worker_id = worker;
|
||||
trace.worker_queue_size = queue_size;
|
||||
trace.worker_queue_capacity = queue_capacity;
|
||||
trace.started_ms = elapsed_ms(started);
|
||||
trace.finished_ms = elapsed_ms(finished);
|
||||
trace.duration_ms = std::max(0.0, trace.finished_ms - trace.started_ms);
|
||||
data.taskflow_workers[worker].tasks.push_back(std::move(trace));
|
||||
}
|
||||
|
||||
detail::Taskflow_Graph_Token detail::Taskflow_Frame_Access::begin_graph(
|
||||
Render_Frame& frame, Task_Graph& taskflow, std::string_view stage) {
|
||||
Taskflow_Graph_Trace graph{};
|
||||
graph.stage = stage;
|
||||
graph.taskflow_name = taskflow.name();
|
||||
graph.nodes = detail::Task_Graph_Access::nodes(taskflow);
|
||||
graph.submitted_ms = std::chrono::duration<double, std::milli>(
|
||||
Clock::now() - frame.d->created_at).count();
|
||||
std::lock_guard lock(frame.d->taskflow_graph_mutex);
|
||||
frame.d->taskflow_graphs.push_back(std::move(graph));
|
||||
return {&frame, frame.d->taskflow_graphs.size() - 1};
|
||||
}
|
||||
|
||||
void detail::Taskflow_Frame_Access::finish_graph(
|
||||
detail::Taskflow_Graph_Token token) noexcept {
|
||||
if (!token.frame) return;
|
||||
auto& data = *token.frame->d;
|
||||
std::lock_guard lock(data.taskflow_graph_mutex);
|
||||
if (token.index >= data.taskflow_graphs.size()) return;
|
||||
auto& graph = data.taskflow_graphs[token.index];
|
||||
graph.finished_ms = std::chrono::duration<double, std::milli>(
|
||||
Clock::now() - data.created_at).count();
|
||||
graph.completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#include "double_buffer/mechanism.hpp"
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace aethera {
|
||||
namespace detail { struct Taskflow_Frame_Access; }
|
||||
enum class Frame_Dimension : std::uint8_t;
|
||||
struct Frame_Statistics_Sample;
|
||||
enum class Frame_Trace_Marker : std::uint8_t {
|
||||
@@ -32,6 +34,9 @@ enum class Frame_Trace_Marker : std::uint8_t {
|
||||
count
|
||||
};
|
||||
enum class Frame_Trace_Measurement : std::uint8_t {
|
||||
plot_tick_queue_ns,
|
||||
plot_update_ns,
|
||||
plot_publish_ns,
|
||||
backend_apply_ns,
|
||||
backend_plan_ns,
|
||||
backend_execute_ns,
|
||||
@@ -57,6 +62,41 @@ struct Frame_Trace_Value {
|
||||
Frame_Trace_Measurement measurement{}; /* 无法表达为公共时间点的原始后端测量类型。 */
|
||||
std::uint64_t value_ns{}; /* 后端直接记录的持续时间,单位为纳秒。 */
|
||||
};
|
||||
struct Taskflow_Graph_Trace {
|
||||
std::string stage{}; /* 本帧中执行该原生 Taskflow 的业务阶段。 */
|
||||
std::string taskflow_name{}; /* Taskflow 原生图名称。 */
|
||||
struct Node {
|
||||
std::uint64_t native_id{}; /* Taskflow Node 的 native hash。 */
|
||||
std::string node_id{}; /* 图路径、业务名和同名序号组成的稳定帧内 ID。 */
|
||||
std::string parent_node_id{}; /* 模块子图所属的业务节点;顶层为空。 */
|
||||
std::string name{}; /* Taskflow 节点业务名称。 */
|
||||
std::string type{}; /* Taskflow 原生 TaskType。 */
|
||||
std::vector<std::uint64_t> predecessors{}; /* 原生直接前驱 hash。 */
|
||||
std::vector<std::uint64_t> successors{}; /* 原生直接后继 hash。 */
|
||||
};
|
||||
std::vector<Node> nodes{}; /* DAG 构造时登记的节点与依赖元信息。 */
|
||||
double submitted_ms{}; /* 相对帧创建时刻的 run 提交时间。 */
|
||||
double finished_ms{}; /* 同步返回或异步 topology 完成时间。 */
|
||||
bool completed{}; /* 对应 topology 是否已经结束。 */
|
||||
};
|
||||
struct Taskflow_Task_Trace {
|
||||
std::uint64_t native_id{}; /* TaskView::hash_value() 返回的原生 Node 身份。 */
|
||||
std::size_t worker_id{}; /* 执行该任务的 Executor worker。 */
|
||||
std::size_t worker_queue_size{}; /* on_entry 时的原生 worker queue_size。 */
|
||||
std::size_t worker_queue_capacity{}; /* on_entry 时的原生 worker queue_capacity。 */
|
||||
double started_ms{}; /* 相对帧创建时刻的 on_entry 时间。 */
|
||||
double finished_ms{}; /* 相对帧创建时刻的 on_exit 时间。 */
|
||||
double duration_ms{}; /* 原生 on_entry 到 on_exit 的持续时间。 */
|
||||
double ready_ms{}; /* 前驱完成或根 run 提交后的估算就绪时间。 */
|
||||
double queue_wait_ms{}; /* ready 到 on_entry 的估算 Executor 排队时间。 */
|
||||
};
|
||||
struct Taskflow_Frame_Trace {
|
||||
Frame_Identity identity{}; /* 该执行图所属逻辑渲染帧。 */
|
||||
std::uint64_t created_time_unix_ns{}; /* 帧创建 Unix 时间,单位纳秒。 */
|
||||
std::size_t worker_count{}; /* 捕获时全局 Executor 的 worker 数。 */
|
||||
std::vector<Taskflow_Graph_Trace> graphs{}; /* 本帧主动执行的业务 DAG 元信息。 */
|
||||
std::vector<Taskflow_Task_Trace> tasks{}; /* 本帧窗口内原生 Observer 完成的任务执行。 */
|
||||
};
|
||||
class Render_Frame : public double_buffer::Pinned {
|
||||
public:
|
||||
explicit Render_Frame(Frame_Identity identity);
|
||||
@@ -66,11 +106,15 @@ public:
|
||||
void mark(Frame_Trace_Marker marker) noexcept;
|
||||
void record(Frame_Trace_Measurement measurement, std::uint64_t value_ns) noexcept;
|
||||
[[nodiscard]] Frame_Statistics_Sample statistics(Frame_Dimension dimension) const;
|
||||
void request_taskflow_trace() noexcept;
|
||||
[[nodiscard]] bool taskflow_trace_requested() const noexcept;
|
||||
[[nodiscard]] Taskflow_Frame_Trace taskflow_trace() const;
|
||||
protected:
|
||||
/* 物理帧槽再次承载新逻辑帧时,重建其唯一身份和诊断时间原点。 */
|
||||
void begin(Frame_Identity identity) noexcept;
|
||||
private:
|
||||
struct Private;
|
||||
friend struct detail::Taskflow_Frame_Access;
|
||||
std::unique_ptr<Private> d; /* 帧身份、时钟原点与原子诊断槽位的唯一所有权。 */
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace aethera {
|
||||
enum class Frame_Dimension : std::uint8_t { two_dimensional, three_dimensional };
|
||||
|
||||
enum class Frame_Statistic : std::uint8_t {
|
||||
plot_tick_queue_ms,
|
||||
plot_update_ms,
|
||||
plot_publish_ms,
|
||||
server_completion_ms,
|
||||
scene_render_ms,
|
||||
event_dispatch_ms,
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
#include "render_common.hpp"
|
||||
#include "Taskflow_Frame_Access.hpp"
|
||||
#include "Task_Graph_Internal.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <stdexcept>
|
||||
#include <taskflow/observer/interface.hpp>
|
||||
#include <taskflow/taskflow.hpp>
|
||||
namespace aethera {
|
||||
namespace {
|
||||
tf::Taskflow& native_taskflow(Task_Graph& graph) noexcept {
|
||||
return *static_cast<tf::Taskflow*>(
|
||||
detail::Task_Graph_Access::native_storage(graph));
|
||||
}
|
||||
|
||||
class Task_Observer : public tf::ObserverInterface {
|
||||
private:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
@@ -18,14 +27,27 @@ private:
|
||||
};
|
||||
struct Worker_Statistics {
|
||||
std::atomic_size_t task_count{};
|
||||
std::atomic_size_t current_queue_size{};
|
||||
std::atomic_size_t current_queue_capacity{};
|
||||
std::atomic_size_t peak_queue_size{};
|
||||
std::atomic_size_t max_queue_capacity{};
|
||||
std::atomic_uint64_t active_task_hash{};
|
||||
std::atomic_uint64_t active_task_started_ns{};
|
||||
std::atomic<tf::TaskType> active_task_type{tf::TaskType::UNDEFINED};
|
||||
std::atomic_uint64_t task_time_ns{};
|
||||
std::atomic_uint64_t busy_time_ns{};
|
||||
std::atomic_uint64_t min_task_time_ns{std::numeric_limits<std::uint64_t>::max()};
|
||||
std::atomic_uint64_t max_task_time_ns{};
|
||||
};
|
||||
std::vector<std::vector<Clock::time_point>> starts;
|
||||
struct Start_Record {
|
||||
Clock::time_point started{}; /* Observer on_entry 时间。 */
|
||||
Render_Frame* frame{}; /* 进入任务时唯一活动的按帧捕获。 */
|
||||
std::size_t queue_size{}; /* 进入任务时 worker 队列深度。 */
|
||||
std::size_t queue_capacity{}; /* 进入任务时 worker 队列容量。 */
|
||||
std::uint64_t native_id{}; /* 嵌套 corun 返回外层任务时恢复其原生身份。 */
|
||||
tf::TaskType type{tf::TaskType::UNDEFINED}; /* 嵌套 corun 返回外层任务时恢复其原生类型。 */
|
||||
};
|
||||
std::vector<std::vector<Start_Record>> starts;
|
||||
std::vector<Clock::time_point> worker_busy_starts;
|
||||
std::unique_ptr<Worker_Statistics[]> worker_statistics;
|
||||
std::size_t worker_statistics_count{};
|
||||
@@ -49,6 +71,9 @@ private:
|
||||
std::atomic_uint64_t longest_task_time_ns{};
|
||||
std::atomic_size_t longest_task_hash{};
|
||||
std::atomic<tf::TaskType> longest_task_type{tf::TaskType::UNDEFINED};
|
||||
std::atomic<std::shared_ptr<const std::string>> longest_task_name{};
|
||||
std::atomic<Render_Frame*> trace_frame{};
|
||||
std::shared_mutex trace_mutex{}; /* 仅按需捕获时保护 Frame* 获取与关闭。 */
|
||||
static std::uint64_t clock_ns(Clock::time_point value) noexcept {
|
||||
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(value.time_since_epoch()).count());
|
||||
}
|
||||
@@ -84,12 +109,37 @@ public:
|
||||
auto active = active_workers.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_max(peak_active_workers, active);
|
||||
}
|
||||
worker_starts.push_back(now);
|
||||
worker_starts.push_back(Start_Record{
|
||||
now, nullptr, worker.queue_size(), worker.queue_capacity(),
|
||||
static_cast<std::uint64_t>(task.hash_value()), task.type()});
|
||||
auto* frame = trace_frame.load(std::memory_order_acquire);
|
||||
/*
|
||||
* 帧租约从 on_entry 持续到对应 on_exit。只在退出时登记写入者会留下
|
||||
* “捕获已关闭、物理帧已复用、迟到 on_exit 仍访问旧 Frame*”的窗口。
|
||||
*/
|
||||
if (frame) {
|
||||
std::shared_lock trace_guard(trace_mutex);
|
||||
frame = trace_frame.load(std::memory_order_acquire);
|
||||
if (frame && detail::Taskflow_Frame_Access::acquire_writer(*frame)) {
|
||||
if (!detail::Taskflow_Frame_Access::contains_task(
|
||||
*frame, static_cast<std::uint64_t>(task.hash_value()))) {
|
||||
detail::Taskflow_Frame_Access::release_writer(*frame);
|
||||
frame = nullptr;
|
||||
}
|
||||
}
|
||||
else frame = nullptr;
|
||||
}
|
||||
worker_starts.back().frame = frame;
|
||||
auto active = active_tasks.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
update_max(peak_active_tasks, active);
|
||||
update_max(peak_queue_size, worker.queue_size());
|
||||
update_max(max_queue_capacity, worker.queue_capacity());
|
||||
auto& worker_state = worker_statistics[worker.id()];
|
||||
worker_state.current_queue_size.store(worker.queue_size(), std::memory_order_relaxed);
|
||||
worker_state.current_queue_capacity.store(worker.queue_capacity(), std::memory_order_relaxed);
|
||||
worker_state.active_task_hash.store(task.hash_value(), std::memory_order_relaxed);
|
||||
worker_state.active_task_started_ns.store(clock_ns(now), std::memory_order_relaxed);
|
||||
worker_state.active_task_type.store(task.type(), std::memory_order_relaxed);
|
||||
update_max(worker_state.peak_queue_size, worker.queue_size());
|
||||
update_max(worker_state.max_queue_capacity, worker.queue_capacity());
|
||||
update_max(max_predecessors, task.num_predecessors());
|
||||
@@ -104,7 +154,7 @@ public:
|
||||
auto& worker_starts = starts[worker.id()];
|
||||
auto start = worker_starts.back();
|
||||
worker_starts.pop_back();
|
||||
auto elapsed = static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(now - start).count());
|
||||
auto elapsed = static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(now - start.started).count());
|
||||
total_execution_time_ns.fetch_add(elapsed, std::memory_order_relaxed);
|
||||
completed_tasks.fetch_add(1, std::memory_order_relaxed);
|
||||
auto& worker_state = worker_statistics[worker.id()];
|
||||
@@ -125,6 +175,9 @@ public:
|
||||
longest, elapsed, std::memory_order_relaxed)) {
|
||||
longest_task_hash.store(task.hash_value(), std::memory_order_relaxed);
|
||||
longest_task_type.store(task.type(), std::memory_order_relaxed);
|
||||
longest_task_name.store(
|
||||
std::make_shared<const std::string>(task.name()),
|
||||
std::memory_order_release);
|
||||
}
|
||||
active_tasks.fetch_sub(1, std::memory_order_relaxed);
|
||||
if (worker_starts.empty()) {
|
||||
@@ -132,8 +185,51 @@ public:
|
||||
worker_busy_time_ns.fetch_add(busy, std::memory_order_relaxed);
|
||||
worker_state.busy_time_ns.fetch_add(busy, std::memory_order_relaxed);
|
||||
active_workers.fetch_sub(1, std::memory_order_relaxed);
|
||||
worker_state.active_task_hash.store(0, std::memory_order_relaxed);
|
||||
worker_state.active_task_started_ns.store(0, std::memory_order_relaxed);
|
||||
worker_state.active_task_type.store(tf::TaskType::UNDEFINED,
|
||||
std::memory_order_relaxed);
|
||||
worker_state.current_queue_size.store(0, std::memory_order_relaxed);
|
||||
worker_state.current_queue_capacity.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
else {
|
||||
const auto& parent = worker_starts.back();
|
||||
worker_state.active_task_hash.store(parent.native_id, std::memory_order_relaxed);
|
||||
worker_state.active_task_started_ns.store(clock_ns(parent.started),
|
||||
std::memory_order_relaxed);
|
||||
worker_state.active_task_type.store(parent.type, std::memory_order_relaxed);
|
||||
worker_state.current_queue_size.store(parent.queue_size,
|
||||
std::memory_order_relaxed);
|
||||
worker_state.current_queue_capacity.store(parent.queue_capacity,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
update_max(last_task_time_ns, clock_ns(now));
|
||||
if (start.frame) {
|
||||
try {
|
||||
detail::Taskflow_Frame_Access::append_task(
|
||||
*start.frame, worker.id(),
|
||||
static_cast<std::uint64_t>(task.hash_value()),
|
||||
start.queue_size, start.queue_capacity, start.started, now);
|
||||
}
|
||||
catch (...) {
|
||||
/* Observer 不能让按需诊断分配失败改变渲染任务的完成语义。 */
|
||||
}
|
||||
detail::Taskflow_Frame_Access::release_writer(*start.frame);
|
||||
}
|
||||
}
|
||||
bool begin_trace(Render_Frame& frame, std::size_t workers) {
|
||||
std::unique_lock guard(trace_mutex);
|
||||
const auto current = trace_frame.load(std::memory_order_acquire);
|
||||
if (current) return current == &frame;
|
||||
detail::Taskflow_Frame_Access::begin_capture(frame, workers);
|
||||
trace_frame.store(&frame, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
void finish_trace(Render_Frame& frame) noexcept {
|
||||
std::unique_lock guard(trace_mutex);
|
||||
if (trace_frame.load(std::memory_order_acquire) != &frame) return;
|
||||
trace_frame.store(nullptr, std::memory_order_release);
|
||||
detail::Taskflow_Frame_Access::finish_capture(frame);
|
||||
}
|
||||
void write_state(Task_Runtime_State& state, std::size_t workers, std::size_t active_topologies) const {
|
||||
state.worker_count = workers;
|
||||
@@ -156,9 +252,11 @@ public:
|
||||
auto last = last_task_time_ns.load(std::memory_order_relaxed);
|
||||
state.observed_wall_time_ns = first && last >= first ? last - first : 0;
|
||||
state.worker_utilization = workers && state.observed_wall_time_ns ? static_cast<double>(state.worker_busy_time_ns) * 100.0 / static_cast<double>(state.observed_wall_time_ns) / static_cast<double>(workers) : 0.0;
|
||||
state.task_types.resize(task_types.size());
|
||||
for (std::size_t i = 0; i < task_types.size(); ++i) {
|
||||
const auto& source = task_types[i];
|
||||
auto& target = state.task_types[i];
|
||||
target.name = std::string(tf::to_string(tf::TASK_TYPES[i]));
|
||||
target.count = source.count.load(std::memory_order_relaxed);
|
||||
target.total_time_ns = source.total_time_ns.load(std::memory_order_relaxed);
|
||||
auto min = source.min_time_ns.load(std::memory_order_relaxed);
|
||||
@@ -166,13 +264,23 @@ public:
|
||||
target.max_time_ns = source.max_time_ns.load(std::memory_order_relaxed);
|
||||
}
|
||||
state.workers.resize(worker_statistics_count);
|
||||
const auto read_time_ns = clock_ns(Clock::now());
|
||||
for (std::size_t i = 0; i < worker_statistics_count; ++i) {
|
||||
const auto& source = worker_statistics[i];
|
||||
auto& target = state.workers[i];
|
||||
target.id = i;
|
||||
target.task_count = source.task_count.load(std::memory_order_relaxed);
|
||||
target.current_queue_size = source.current_queue_size.load(std::memory_order_relaxed);
|
||||
target.current_queue_capacity = source.current_queue_capacity.load(std::memory_order_relaxed);
|
||||
target.peak_observed_queue_size = source.peak_queue_size.load(std::memory_order_relaxed);
|
||||
target.max_observed_queue_capacity = source.max_queue_capacity.load(std::memory_order_relaxed);
|
||||
target.active_task_hash = source.active_task_hash.load(std::memory_order_relaxed);
|
||||
const auto active_started = source.active_task_started_ns.load(std::memory_order_relaxed);
|
||||
target.active_task_time_ns = active_started && read_time_ns >= active_started
|
||||
? read_time_ns - active_started : 0;
|
||||
const auto active_type = source.active_task_type.load(std::memory_order_relaxed);
|
||||
target.active_task_type = active_started
|
||||
? std::string(tf::to_string(active_type)) : std::string{};
|
||||
target.task_time_ns = source.task_time_ns.load(std::memory_order_relaxed);
|
||||
target.busy_time_ns = source.busy_time_ns.load(std::memory_order_relaxed);
|
||||
target.idle_time_ns = state.observed_wall_time_ns > target.busy_time_ns ? state.observed_wall_time_ns - target.busy_time_ns : 0;
|
||||
@@ -183,8 +291,10 @@ public:
|
||||
}
|
||||
state.longest_task_time_ns = longest_task_time_ns.load(std::memory_order_relaxed);
|
||||
state.longest_task_hash = longest_task_hash.load(std::memory_order_relaxed);
|
||||
state.longest_task_name.clear();
|
||||
state.longest_task_type = longest_task_type.load(std::memory_order_relaxed);
|
||||
const auto longest_name = longest_task_name.load(std::memory_order_acquire);
|
||||
state.longest_task_name = longest_name ? *longest_name : std::string{};
|
||||
state.longest_task_type = std::string(tf::to_string(
|
||||
longest_task_type.load(std::memory_order_relaxed)));
|
||||
}
|
||||
};
|
||||
class Task_Resource : Pinned {
|
||||
@@ -200,15 +310,22 @@ private:
|
||||
double_buffer::detail::State_Callback_Storage<Task_Runtime_State> state_callbacks;
|
||||
std::recursive_mutex state_mutex;
|
||||
std::atomic_bool state_callback_enabled{};
|
||||
std::atomic_bool executor_ready{};
|
||||
void create_executor(std::size_t workers, std::shared_ptr<tf::WorkerInterface> worker_interface) {
|
||||
executor_ready.store(false, std::memory_order_relaxed);
|
||||
executor = std::make_unique<tf::Executor>(workers, std::move(worker_interface));
|
||||
observer.reset();
|
||||
observer = executor->make_observer<Task_Observer>();
|
||||
state = {};
|
||||
executor_ready.store(true, std::memory_order_release);
|
||||
}
|
||||
void ensure_executor() {
|
||||
/* 正常渲染热路径只读取一次原子位;互斥量仅处理首次惰性初始化。 */
|
||||
if (executor_ready.load(std::memory_order_acquire)) return;
|
||||
std::lock_guard guard(state_mutex);
|
||||
if (executor) return;
|
||||
executor = std::make_unique<tf::Executor>();
|
||||
observer = executor->make_observer<Task_Observer>();
|
||||
executor_ready.store(true, std::memory_order_release);
|
||||
}
|
||||
void publish_state() {
|
||||
if (!state_callback_enabled.load(std::memory_order_acquire)) return;
|
||||
@@ -235,7 +352,7 @@ public:
|
||||
completed_taskflows.store(0, std::memory_order_relaxed);
|
||||
failed_taskflows.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
std::uint64_t run(tf::Taskflow& taskflow) {
|
||||
std::uint64_t run(Task_Graph& taskflow) {
|
||||
ensure_executor();
|
||||
auto active = active_taskflows.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
auto peak = peak_active_taskflows.load(std::memory_order_relaxed);
|
||||
@@ -243,9 +360,9 @@ public:
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
try {
|
||||
if (executor->this_worker())
|
||||
executor->corun(taskflow);
|
||||
executor->corun(native_taskflow(taskflow));
|
||||
else
|
||||
executor->run(taskflow).get();
|
||||
executor->run(native_taskflow(taskflow)).get();
|
||||
}
|
||||
catch (...) {
|
||||
active_taskflows.fetch_sub(1, std::memory_order_relaxed);
|
||||
@@ -259,24 +376,48 @@ public:
|
||||
publish_state();
|
||||
return elapsed;
|
||||
}
|
||||
void run(tf::Taskflow& taskflow, std::function<void()> completion) {
|
||||
void run(Task_Graph& taskflow, std::function<void()> completion) {
|
||||
if (!completion) throw std::invalid_argument("Taskflow completion is empty");
|
||||
ensure_executor();
|
||||
auto active = active_taskflows.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
auto peak = peak_active_taskflows.load(std::memory_order_relaxed);
|
||||
while (peak < active && !peak_active_taskflows.compare_exchange_weak(
|
||||
peak, active, std::memory_order_relaxed)) {}
|
||||
executor->run(taskflow, [this, completion = std::move(completion)]() mutable {
|
||||
executor->run(native_taskflow(taskflow), [this, completion = std::move(completion)]() mutable {
|
||||
active_taskflows.fetch_sub(1, std::memory_order_relaxed);
|
||||
completed_taskflows.fetch_add(1, std::memory_order_relaxed);
|
||||
publish_state();
|
||||
completion();
|
||||
});
|
||||
}
|
||||
void schedule(std::function<void()> task) {
|
||||
std::uint64_t run(Task_Graph& taskflow, Render_Frame& frame,
|
||||
std::string_view stage) {
|
||||
const auto token = detail::Taskflow_Frame_Access::begin_graph(
|
||||
frame, taskflow, stage);
|
||||
try {
|
||||
const auto elapsed = run(taskflow);
|
||||
detail::Taskflow_Frame_Access::finish_graph(token);
|
||||
return elapsed;
|
||||
}
|
||||
catch (...) {
|
||||
detail::Taskflow_Frame_Access::finish_graph(token);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
void run(Task_Graph& taskflow, Render_Frame& frame, std::string_view stage,
|
||||
std::function<void()> completion) {
|
||||
const auto token = detail::Taskflow_Frame_Access::begin_graph(
|
||||
frame, taskflow, stage);
|
||||
run(taskflow, [token, completion = std::move(completion)]() mutable {
|
||||
detail::Taskflow_Frame_Access::finish_graph(token);
|
||||
completion();
|
||||
});
|
||||
}
|
||||
void schedule(std::string name, std::function<void()> task) {
|
||||
if (!task) throw std::invalid_argument("Taskflow scheduled task is empty");
|
||||
if (name.empty()) throw std::invalid_argument("Taskflow scheduled task name is empty");
|
||||
ensure_executor();
|
||||
executor->silent_async(std::move(task));
|
||||
executor->silent_async(std::move(name), std::move(task));
|
||||
}
|
||||
std::pmr::memory_resource* memory_resource() const noexcept {
|
||||
return memory;
|
||||
@@ -284,7 +425,6 @@ public:
|
||||
void set_state_callback(std::function<void(const Task_Runtime_State&)> callback) {
|
||||
ensure_executor();
|
||||
std::lock_guard guard(state_mutex);
|
||||
if (!observer) observer = executor->make_observer<Task_Observer>();
|
||||
state_callbacks.template set<Task_Runtime_State_Tag>(std::move(callback));
|
||||
state_callback_enabled.store(true, std::memory_order_release);
|
||||
}
|
||||
@@ -293,13 +433,34 @@ public:
|
||||
state_callbacks.template clear<Task_Runtime_State_Tag>();
|
||||
state_callback_enabled.store(false, std::memory_order_release);
|
||||
}
|
||||
bool begin_trace(Render_Frame& frame) {
|
||||
ensure_executor();
|
||||
return observer->begin_trace(frame, executor->num_workers());
|
||||
}
|
||||
void finish_trace(Render_Frame& frame) noexcept {
|
||||
if (observer) observer->finish_trace(frame);
|
||||
}
|
||||
Task_Runtime_State runtime_state() {
|
||||
ensure_executor();
|
||||
std::lock_guard guard(state_mutex);
|
||||
Task_Runtime_State result{};
|
||||
observer->write_state(result, executor->num_workers(), executor->num_topologies());
|
||||
result.active_taskflow_count = active_taskflows.load(std::memory_order_relaxed);
|
||||
result.peak_active_taskflow_count = peak_active_taskflows.load(std::memory_order_relaxed);
|
||||
result.completed_taskflow_count = completed_taskflows.load(std::memory_order_relaxed);
|
||||
result.failed_taskflow_count = failed_taskflows.load(std::memory_order_relaxed);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
void initialize_runtime(std::size_t workers, std::shared_ptr<tf::WorkerInterface> worker_interface, Pmr pmr) {
|
||||
Task_Resource::instance().initialize(workers, std::move(worker_interface), pmr);
|
||||
void initialize_runtime(std::size_t workers, Pmr pmr) {
|
||||
Task_Resource::instance().initialize(workers, nullptr, pmr);
|
||||
}
|
||||
void schedule_task(std::function<void()> task) {
|
||||
Task_Resource::instance().schedule(std::move(task));
|
||||
void schedule_task(std::string name, std::function<void()> task) {
|
||||
Task_Resource::instance().schedule(std::move(name), std::move(task));
|
||||
}
|
||||
Task_Runtime_State task_runtime_state() {
|
||||
return Task_Resource::instance().runtime_state();
|
||||
}
|
||||
namespace detail {
|
||||
void set_runtime_state_callback_impl(std::function<void(const Task_Runtime_State&)> callback) {
|
||||
@@ -308,12 +469,28 @@ void set_runtime_state_callback_impl(std::function<void(const Task_Runtime_State
|
||||
void clear_runtime_state_callback_impl() {
|
||||
Task_Resource::instance().clear_state_callback();
|
||||
}
|
||||
std::uint64_t run_taskflow(tf::Taskflow& taskflow) {
|
||||
std::uint64_t run_taskflow(Task_Graph& taskflow) {
|
||||
return Task_Resource::instance().run(taskflow);
|
||||
}
|
||||
void run_taskflow(tf::Taskflow& taskflow, std::function<void()> completion) {
|
||||
void run_taskflow(Task_Graph& taskflow, std::function<void()> completion) {
|
||||
Task_Resource::instance().run(taskflow, std::move(completion));
|
||||
}
|
||||
std::uint64_t run_taskflow(Task_Graph& taskflow, Render_Frame& frame,
|
||||
std::string_view stage) {
|
||||
return Task_Resource::instance().run(taskflow, frame, stage);
|
||||
}
|
||||
void run_taskflow(Task_Graph& taskflow, Render_Frame& frame,
|
||||
std::string_view stage, std::function<void()> completion) {
|
||||
Task_Resource::instance().run(taskflow, frame, stage, std::move(completion));
|
||||
}
|
||||
bool begin_taskflow_trace(Render_Frame& frame) {
|
||||
return !frame.taskflow_trace_requested() ||
|
||||
Task_Resource::instance().begin_trace(frame);
|
||||
}
|
||||
void finish_taskflow_trace(Render_Frame& frame) noexcept {
|
||||
if (frame.taskflow_trace_requested())
|
||||
Task_Resource::instance().finish_trace(frame);
|
||||
}
|
||||
std::pmr::memory_resource* task_memory_resource() noexcept {
|
||||
return Task_Resource::instance().memory_resource();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "double_buffer/model.hpp"
|
||||
#include "frame.hpp"
|
||||
#include "Task_Graph.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -10,7 +11,6 @@
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <taskflow/taskflow.hpp>
|
||||
namespace aethera {
|
||||
using double_buffer::Pinned;
|
||||
using double_buffer::Def;
|
||||
@@ -32,6 +32,7 @@ struct Prepare_Data_Tag {};
|
||||
struct Task_Runtime_State_Tag {};
|
||||
/* 单一 Taskflow 任务类型的累计统计。 */
|
||||
struct Task_Type_State {
|
||||
std::string name{};
|
||||
std::size_t count{};
|
||||
std::uint64_t total_time_ns{};
|
||||
std::uint64_t min_time_ns{};
|
||||
@@ -42,8 +43,13 @@ struct Task_Type_State {
|
||||
struct Task_Worker_State {
|
||||
std::size_t id{};
|
||||
std::size_t task_count{};
|
||||
std::size_t current_queue_size{}; /* 当前活跃任务进入时观察到的 Worker 队列深度。 */
|
||||
std::size_t current_queue_capacity{}; /* 当前活跃任务进入时观察到的 Worker 队列容量。 */
|
||||
std::size_t peak_observed_queue_size{};
|
||||
std::size_t max_observed_queue_capacity{};
|
||||
std::uint64_t active_task_hash{}; /* 当前最内层原生任务身份;空闲时为 0。 */
|
||||
std::uint64_t active_task_time_ns{}; /* 当前任务从 on_entry 到本次读取已经持续的时间。 */
|
||||
std::string active_task_type{}; /* 当前任务的 Taskflow 原生 TaskType;空闲时为空。 */
|
||||
std::uint64_t task_time_ns{};
|
||||
std::uint64_t busy_time_ns{};
|
||||
std::uint64_t idle_time_ns{};
|
||||
@@ -77,13 +83,13 @@ struct Task_Runtime_State : State_Type<Task_Runtime_State_Tag> {
|
||||
std::size_t max_weak_dependencies{};
|
||||
std::size_t longest_task_hash{};
|
||||
std::string longest_task_name;
|
||||
tf::TaskType longest_task_type{tf::TaskType::UNDEFINED};
|
||||
std::string longest_task_type;
|
||||
std::uint64_t longest_task_time_ns{};
|
||||
std::uint64_t total_task_time_ns{};
|
||||
std::uint64_t worker_busy_time_ns{};
|
||||
std::uint64_t observed_wall_time_ns{};
|
||||
double worker_utilization{};
|
||||
std::array<Task_Type_State, tf::TASK_TYPES.size()> task_types{};
|
||||
std::vector<Task_Type_State> task_types;
|
||||
std::vector<Task_Worker_State> workers;
|
||||
bool operator==(const Task_Runtime_State&) const = default;
|
||||
};
|
||||
@@ -93,10 +99,11 @@ struct Task_Runtime_State : State_Type<Task_Runtime_State_Tag> {
|
||||
* 未主动调用时运行时会在第一次执行 Taskflow 时按 Taskflow 默认配置惰性创建 Executor。
|
||||
*/
|
||||
void initialize_runtime(std::size_t workers = std::thread::hardware_concurrency(),
|
||||
std::shared_ptr<tf::WorkerInterface> worker_interface = nullptr,
|
||||
Pmr pmr = {});
|
||||
/* 把独立业务任务提交给全局 Taskflow worker;任务不得执行阻塞式设备等待。 */
|
||||
void schedule_task(std::function<void()> task);
|
||||
/* 把具名独立业务任务提交给全局 Taskflow worker;任务不得执行阻塞式设备等待。 */
|
||||
void schedule_task(std::string name, std::function<void()> task);
|
||||
/* 读取全局 Executor 的累计状态;计算只读取原子计数,不触发逐任务导出。 */
|
||||
[[nodiscard]] Task_Runtime_State task_runtime_state();
|
||||
/* 为全局 Taskflow 运行时状态注册回调;Tag 目前只接受 Task_Runtime_State_Tag。 */
|
||||
template <std::same_as<Task_Runtime_State_Tag> Tag, std::invocable<const Task_Runtime_State&> Callback>
|
||||
void set_runtime_state_callback(Callback&& callback);
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
namespace aethera::detail {
|
||||
void set_runtime_state_callback_impl(std::function<void(const Task_Runtime_State&)> callback);
|
||||
void clear_runtime_state_callback_impl();
|
||||
std::uint64_t run_taskflow(tf::Taskflow& taskflow);
|
||||
void run_taskflow(tf::Taskflow& taskflow, std::function<void()> completion);
|
||||
std::uint64_t run_taskflow(Task_Graph& taskflow);
|
||||
void run_taskflow(Task_Graph& taskflow, std::function<void()> completion);
|
||||
std::uint64_t run_taskflow(Task_Graph& taskflow, Render_Frame& frame, std::string_view stage);
|
||||
void run_taskflow(Task_Graph& taskflow, Render_Frame& frame, std::string_view stage,
|
||||
std::function<void()> completion);
|
||||
bool begin_taskflow_trace(Render_Frame& frame);
|
||||
void finish_taskflow_trace(Render_Frame& frame) noexcept;
|
||||
std::pmr::memory_resource* task_memory_resource() noexcept;
|
||||
}
|
||||
namespace aethera {
|
||||
|
||||
@@ -4,10 +4,10 @@ std::optional<double> Renderable::event_routing_distance(const Event& event) con
|
||||
const auto& data = static_cast<const Private&>(*d);
|
||||
return data.event_routing_distance_run ? data.event_routing_distance_run(this, event) : std::nullopt;
|
||||
}
|
||||
tf::Taskflow& Renderable::prepare_taskflow() {
|
||||
Task_Graph& Renderable::prepare_taskflow() {
|
||||
return static_cast<Private&>(*d).prepare_extension;
|
||||
}
|
||||
tf::Taskflow& Renderable::paint_taskflow() {
|
||||
Task_Graph& Renderable::paint_taskflow() {
|
||||
return static_cast<Private&>(*d).paint_extension;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ concept Prepare_Data_Renderable = Attached<T> && requires(typename T::Private& p
|
||||
};
|
||||
/*
|
||||
* Prepare 子图模式定制点。
|
||||
* 最终对象的 Private 提供 tf::Taskflow build_prepare_graph(T* object, const T::State& state) 即满足。
|
||||
* 最终对象的 Private 提供 Task_Graph build_prepare_graph(T* object, const T::State& state) 即满足。
|
||||
* 子图首次执行前一定构建,之后由 should_rebuild_prepare_graph(...) 决定是否重建。
|
||||
*/
|
||||
template <typename T>
|
||||
concept Prepare_Graph_Renderable = Attached<T> && requires(typename T::Private& private_data, T* object, const typename T::Prop& prop) {
|
||||
{ private_data.build_prepare_graph(object, prop) } -> std::same_as<tf::Taskflow>;
|
||||
{ private_data.build_prepare_graph(object, prop) } -> std::same_as<Task_Graph>;
|
||||
};
|
||||
/* 最终 Private 声明 No_Prepare 时,该 Renderable 不参与 Prepare 阶段。 */
|
||||
template <typename T>
|
||||
@@ -44,12 +44,12 @@ concept Paint_Data_Renderable = Attached<T> && requires(typename T::Private& pri
|
||||
};
|
||||
/*
|
||||
* Paint 子图模式定制点。
|
||||
* 最终对象的 Private 提供 tf::Taskflow build_paint_graph(T* object, const T::State& state) 即满足。
|
||||
* 最终对象的 Private 提供 Task_Graph build_paint_graph(T* object, const T::State& state) 即满足。
|
||||
* 子图首次执行前一定构建,之后由 should_rebuild_paint_graph(...) 决定是否重建。
|
||||
*/
|
||||
template <typename T>
|
||||
concept Paint_Graph_Renderable = Attached<T> && requires(typename T::Private& private_data, T* object, const typename T::Prop& prop) {
|
||||
{ private_data.build_paint_graph(object, prop) } -> std::same_as<tf::Taskflow>;
|
||||
{ private_data.build_paint_graph(object, prop) } -> std::same_as<Task_Graph>;
|
||||
};
|
||||
/*
|
||||
* 最终可交给 Scene 执行的 Renderable 契约。
|
||||
@@ -89,15 +89,15 @@ struct Renderable : Def<Renderable, Root> {
|
||||
/* 返回该对象参与当前事件竞争时的几何距离;无值表示沿用普通绘制层级路由。 */
|
||||
[[nodiscard]] std::optional<double> event_routing_distance(const Event& event) const;
|
||||
/*
|
||||
* 返回 Prepare 阶段完成后执行的直接 Taskflow 扩展端口。
|
||||
* tf::Taskflow 只能在该 Renderable 所属 Scene 没有运行时修改;禁止在图执行期间 emplace/erase/clear。
|
||||
* 返回 Prepare 阶段完成后执行的业务 DAG 扩展端口。
|
||||
* Task_Graph 只能在该 Renderable 所属 Scene 没有运行时修改。
|
||||
*/
|
||||
[[nodiscard]] tf::Taskflow& prepare_taskflow();
|
||||
[[nodiscard]] Task_Graph& prepare_taskflow();
|
||||
/*
|
||||
* 返回 Paint 阶段完成后执行的直接 Taskflow 扩展端口。
|
||||
* tf::Taskflow 只能在该 Renderable 所属 Scene 没有运行时修改;禁止在图执行期间 emplace/erase/clear。
|
||||
* 返回 Paint 阶段完成后执行的业务 DAG 扩展端口。
|
||||
* Task_Graph 只能在该 Renderable 所属 Scene 没有运行时修改。
|
||||
*/
|
||||
[[nodiscard]] tf::Taskflow& paint_taskflow();
|
||||
[[nodiscard]] Task_Graph& paint_taskflow();
|
||||
private:
|
||||
/* 数据模式的内部调度入口:执行最终对象 prepare_data(...),再触发各 CRTP 层 after_prepare_data(...)。 */
|
||||
template <Prepare_Data_Renderable Object>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <typeinfo>
|
||||
namespace aethera {
|
||||
struct Renderable::Private : Prev_Private {
|
||||
/*
|
||||
@@ -11,7 +13,7 @@ struct Renderable::Private : Prev_Private {
|
||||
using Run_Predicate = bool (*)(Root*, bool);
|
||||
using Rebuild_Predicate = bool (*)(Root*);
|
||||
using Stage_Run = void (*)(Root*);
|
||||
using Graph_Builder = tf::Taskflow (*)(Root*);
|
||||
using Graph_Builder = Task_Graph (*)(Root*);
|
||||
using State_Get = State* (*)(Root*);
|
||||
using State_Notify = void (*)(Root*);
|
||||
using Event_Run = void (*)(Root*, const Event&);
|
||||
@@ -33,15 +35,16 @@ struct Renderable::Private : Prev_Private {
|
||||
Stage_Dispatch prepare; /* Prepare 阶段分派。 */
|
||||
Stage_Dispatch paint; /* Paint 阶段分派。 */
|
||||
State_Dispatch state; /* Renderable 状态访问与发布分派。 */
|
||||
std::string_view business_name; /* 最终 Renderable 类型的诊断业务名。 */
|
||||
};
|
||||
const Dispatch* dispatch{}; /* 绑定最终对象类型后指向其静态分派表。 */
|
||||
Event_Run event_run{}; /* 最终 Private 具备事件能力时的无虚函数入口。 */
|
||||
Event_Routing_Distance_Run event_routing_distance_run{}; /* 可选事件候选距离;Scene 路由规则按需查询。 */
|
||||
Color_Cache_Visit color_cache_visit{}; /* 最终对象存在 Color_Cache Buffer 时访问本轮写入结果。 */
|
||||
std::unique_ptr<tf::Taskflow> prepare_graph; /* Prepare 子图模式的当前构建产物。 */
|
||||
std::unique_ptr<tf::Taskflow> paint_graph; /* Paint 子图模式的当前构建产物。 */
|
||||
tf::Taskflow prepare_extension{}; /* 外部直接续写的 Prepare 完成图;不参与内部子图重建。 */
|
||||
tf::Taskflow paint_extension{}; /* 外部直接续写的 Paint 完成图;不参与内部子图重建。 */
|
||||
std::unique_ptr<Task_Graph> prepare_graph; /* Prepare 子图模式的当前构建产物。 */
|
||||
std::unique_ptr<Task_Graph> paint_graph; /* Paint 子图模式的当前构建产物。 */
|
||||
Task_Graph prepare_extension{"renderable.prepare.extension"}; /* 外部直接续写的 Prepare 完成图。 */
|
||||
Task_Graph paint_extension{"renderable.paint.extension"}; /* 外部直接续写的 Paint 完成图。 */
|
||||
bool prepare_graph_built{}; /* Prepare 子图是否至少成功构建过一次。 */
|
||||
bool paint_graph_built{}; /* Paint 子图是否至少成功构建过一次。 */
|
||||
/* CRTP 可覆盖:决定已选中子图模式的 Prepare 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */
|
||||
@@ -112,6 +115,14 @@ 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);
|
||||
static const std::string business_name = [] {
|
||||
std::string name = typeid(typename Object::Attached_Object).name();
|
||||
for (const std::string_view prefix : {"struct ", "class "})
|
||||
if (name.starts_with(prefix)) name.erase(0, prefix.size());
|
||||
if (const auto separator = name.rfind("::"); separator != std::string::npos)
|
||||
name.erase(0, separator + 2);
|
||||
return name;
|
||||
}();
|
||||
static const Private::Dispatch dispatch{
|
||||
{
|
||||
[](Root* root, bool dirty) {
|
||||
@@ -202,7 +213,8 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
||||
private_data.state.advance();
|
||||
value->template notify_state<Renderable::Base_Tag>();
|
||||
}
|
||||
}
|
||||
},
|
||||
business_name
|
||||
};
|
||||
data.dispatch = &dispatch;
|
||||
object->template mark_dirty<Prepare_Data_Tag>();
|
||||
|
||||
+32
-26
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "Task_Graph_Internal.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <span>
|
||||
@@ -32,7 +33,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<Task_Graph> taskflow; /* 当前已构建的总业务 DAG;为空表示尚未构建。 */
|
||||
};
|
||||
template <Attached Object>
|
||||
void Scene::Private::bind_private_crtp(Object* object) {
|
||||
@@ -67,7 +68,10 @@ void Scene::Private::process(Object* object, Render_Frame* frame, Callback&& cal
|
||||
auto& state = static_cast<State&>(*private_data.state.pending);
|
||||
state.taskflow_execution_time_ns = 0;
|
||||
if (frame) frame->mark(Frame_Trace_Marker::prepare_started);
|
||||
if (runtime->taskflow && !runtime->taskflow->empty()) state.taskflow_execution_time_ns = detail::run_taskflow(*runtime->taskflow);
|
||||
if (runtime->taskflow && !runtime->taskflow->empty())
|
||||
state.taskflow_execution_time_ns = frame && frame->taskflow_trace_requested()
|
||||
? detail::run_taskflow(*runtime->taskflow, *frame, "scene.prepare")
|
||||
: detail::run_taskflow(*runtime->taskflow);
|
||||
if (frame) frame->mark(Frame_Trace_Marker::prepare_finished);
|
||||
state.event_statistics = event_statistics.state();
|
||||
private_data.state.advance();
|
||||
@@ -102,7 +106,7 @@ void Scene::Private::after_advance(Object* object,
|
||||
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>();
|
||||
if (!runtime->taskflow) runtime->taskflow = std::make_unique<Task_Graph>("scene.prepare");
|
||||
auto& taskflow = *runtime->taskflow;
|
||||
std::pmr::unordered_set<Renderable*> renderables{resource};
|
||||
prepare_dependencies.for_each_bound(
|
||||
@@ -113,8 +117,8 @@ void Scene::Private::after_advance(Object* object,
|
||||
scene_state.renderable_count = renderables.size();
|
||||
taskflow.clear();
|
||||
struct Stage_Tasks {
|
||||
tf::Task prepare_entry; /* Prepare 条件任务,作为该阶段依赖入口。 */
|
||||
tf::Task prepare_exit; /* Prepare 完成任务,作为该阶段依赖出口。 */
|
||||
Task_Node prepare_entry; /* Prepare 条件任务,作为该阶段依赖入口。 */
|
||||
Task_Node prepare_exit; /* Prepare 完成任务,作为该阶段依赖出口。 */
|
||||
};
|
||||
std::pmr::unordered_map<Root*, Stage_Tasks> stage_tasks{resource};
|
||||
for (auto* renderable : renderables) {
|
||||
@@ -122,7 +126,8 @@ void Scene::Private::after_advance(Object* object,
|
||||
auto* data = prepare_dependencies.private_data(root);
|
||||
if (!data) continue;
|
||||
auto* dispatch = data->dispatch;
|
||||
auto prepare_if = taskflow.emplace([data, dispatch, root] {
|
||||
const auto task_prefix = std::string(dispatch->business_name) + ".prepare";
|
||||
auto prepare_if = taskflow.add_condition(task_prefix + ".condition", [data, dispatch, root] {
|
||||
auto& state = *dispatch->state.pending(root);
|
||||
state.prepare_graph_rebuilt = false;
|
||||
state.prepare_execution_time_ns = 0;
|
||||
@@ -134,7 +139,7 @@ void Scene::Private::after_advance(Object* object,
|
||||
state.prepare_graph_rebuilt = true;
|
||||
root->template mark_dirty<Prepare_Data_Tag>();
|
||||
}
|
||||
state.prepare_task_count = data->prepare_graph->num_tasks();
|
||||
state.prepare_task_count = data->prepare_graph->size();
|
||||
}
|
||||
else {
|
||||
state.prepare_task_count = dispatch->prepare.run ? 1 : 0;
|
||||
@@ -148,20 +153,20 @@ void Scene::Private::after_advance(Object* object,
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count());
|
||||
}
|
||||
return state.prepare_executed ? 0 : 1;
|
||||
}).name("renderable.prepare.condition");
|
||||
tf::Task prepare_run;
|
||||
});
|
||||
Task_Node prepare_run;
|
||||
if (dispatch->prepare.builder) {
|
||||
if (!data->prepare_graph) data->prepare_graph = std::make_unique<tf::Taskflow>();
|
||||
prepare_run = taskflow.composed_of(*data->prepare_graph).name("renderable.prepare.graph");
|
||||
if (!data->prepare_graph) data->prepare_graph = std::make_unique<Task_Graph>(task_prefix + ".graph");
|
||||
prepare_run = taskflow.compose(task_prefix + ".graph", *data->prepare_graph);
|
||||
}
|
||||
else {
|
||||
prepare_run = taskflow.emplace([dispatch, root] {
|
||||
prepare_run = taskflow.add(task_prefix + ".data", [dispatch, root] {
|
||||
if (dispatch->prepare.run) dispatch->prepare.run(root);
|
||||
}).name("renderable.prepare.data");
|
||||
});
|
||||
}
|
||||
auto prepare_extension = taskflow.composed_of(data->prepare_extension)
|
||||
.name("renderable.prepare.extension");
|
||||
auto prepare_done = taskflow.emplace([dispatch, root] {
|
||||
auto prepare_extension = taskflow.compose(task_prefix + ".extension",
|
||||
data->prepare_extension);
|
||||
auto prepare_done = taskflow.add(task_prefix + ".complete", [dispatch, root] {
|
||||
auto& state = *dispatch->state.pending(root);
|
||||
if (state.prepare_executed) {
|
||||
root->template take_dirty<Prepare_Data_Tag>();
|
||||
@@ -171,8 +176,9 @@ void Scene::Private::after_advance(Object* object,
|
||||
state.prepare_execution_time_ns = finished - state.prepare_execution_time_ns;
|
||||
}
|
||||
dispatch->state.publish(root);
|
||||
}).name("renderable.prepare.complete");
|
||||
prepare_if.precede(prepare_run, prepare_done);
|
||||
});
|
||||
prepare_if.precede(prepare_run);
|
||||
prepare_if.precede(prepare_done);
|
||||
prepare_run.precede(prepare_extension);
|
||||
prepare_extension.precede(prepare_done);
|
||||
stage_tasks.emplace(root, Stage_Tasks{prepare_if, prepare_done});
|
||||
@@ -203,17 +209,17 @@ void Scene::Private::after_advance(Object* object,
|
||||
);
|
||||
};
|
||||
connect_dependencies(prepare_dependencies);
|
||||
scene_state.taskflow_task_count = taskflow.num_tasks();
|
||||
scene_state.taskflow_task_count = taskflow.size();
|
||||
scene_state.taskflow_dependency_count = 0;
|
||||
scene_state.taskflow_max_predecessors = 0;
|
||||
scene_state.taskflow_max_successors = 0;
|
||||
taskflow.for_each_task(
|
||||
[&](tf::Task task) {
|
||||
scene_state.taskflow_dependency_count += task.num_successors();
|
||||
scene_state.taskflow_max_predecessors = std::max(scene_state.taskflow_max_predecessors, task.num_predecessors());
|
||||
scene_state.taskflow_max_successors = std::max(scene_state.taskflow_max_successors, task.num_successors());
|
||||
}
|
||||
);
|
||||
for (const auto& node : detail::Task_Graph_Access::nodes(taskflow)) {
|
||||
scene_state.taskflow_dependency_count += node.successors.size();
|
||||
scene_state.taskflow_max_predecessors = std::max(
|
||||
scene_state.taskflow_max_predecessors, node.predecessors.size());
|
||||
scene_state.taskflow_max_successors = std::max(
|
||||
scene_state.taskflow_max_successors, node.successors.size());
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "scene.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <unordered_set>
|
||||
namespace {
|
||||
struct Direct_Renderable : double_buffer::Def<Direct_Renderable, aethera::Renderable> {
|
||||
struct Prop : Prev_Prop {};
|
||||
@@ -28,10 +30,10 @@ struct Graph_Renderable : double_buffer::Def<Graph_Renderable, aethera::Renderab
|
||||
int paint_calls{};
|
||||
int prepare_builds{};
|
||||
bool rebuild_prepare{};
|
||||
tf::Taskflow build_prepare_graph(double_buffer::Attached auto*, const Prop&) {
|
||||
aethera::Task_Graph build_prepare_graph(double_buffer::Attached auto*, const Prop&) {
|
||||
++prepare_builds;
|
||||
tf::Taskflow graph;
|
||||
graph.emplace([this] { ++prepare_calls; }).name("test.prepare.graph.task");
|
||||
aethera::Task_Graph graph{"test.prepare.graph"};
|
||||
graph.add("task", [this] { ++prepare_calls; });
|
||||
return graph;
|
||||
}
|
||||
bool should_rebuild_prepare_graph(double_buffer::Attached auto*, const Prop&) {
|
||||
@@ -94,8 +96,8 @@ TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) {
|
||||
auto scene = build_object<Scene>();
|
||||
add_renderable(*scene, renderable.get());
|
||||
int prepare_extension_calls{};
|
||||
renderable->prepare_taskflow().emplace(
|
||||
[&] { ++prepare_extension_calls; }).name("test.prepare.extension");
|
||||
renderable->prepare_taskflow().add(
|
||||
"test.prepare.extension", [&] { ++prepare_extension_calls; });
|
||||
scene->process([](const auto&) {});
|
||||
auto& data = renderable->data_for_test();
|
||||
auto& base = static_cast<aethera::Renderable::Private&>(data);
|
||||
@@ -211,3 +213,45 @@ TEST(scene_condition, upstream_change_makes_downstream_run_in_same_taskflow) {
|
||||
EXPECT_EQ(source_data.prepare_calls, 2);
|
||||
EXPECT_EQ(target_data.prepare_calls, 2);
|
||||
}
|
||||
|
||||
TEST(task_graph_observer, business_dag_and_native_execution_share_node_identity) {
|
||||
aethera::initialize_runtime(2);
|
||||
std::atomic_int completed{};
|
||||
aethera::Task_Graph child{"test.visual"};
|
||||
auto prepare = child.add("prepare.samples", [&] { completed.fetch_add(1); });
|
||||
auto paint = child.add("paint.visual", [&] { completed.fetch_add(1); });
|
||||
prepare.precede(paint);
|
||||
aethera::Task_Graph frame_graph{"test.frame"};
|
||||
frame_graph.compose("spectrum", child);
|
||||
|
||||
aethera::Render_Frame frame{{41, 73}};
|
||||
frame.request_taskflow_trace();
|
||||
ASSERT_TRUE(aethera::detail::begin_taskflow_trace(frame));
|
||||
aethera::detail::run_taskflow(frame_graph, frame, "test.scene.paint");
|
||||
aethera::detail::finish_taskflow_trace(frame);
|
||||
|
||||
EXPECT_EQ(completed.load(), 2);
|
||||
const auto trace = frame.taskflow_trace();
|
||||
ASSERT_EQ(trace.graphs.size(), 1u);
|
||||
EXPECT_EQ(trace.identity, (aethera::Frame_Identity{41, 73}));
|
||||
EXPECT_EQ(trace.graphs.front().stage, "test.scene.paint");
|
||||
EXPECT_TRUE(trace.graphs.front().completed);
|
||||
ASSERT_EQ(trace.graphs.front().nodes.size(), 3u);
|
||||
const auto module = std::ranges::find(
|
||||
trace.graphs.front().nodes, "spectrum",
|
||||
&aethera::Taskflow_Graph_Trace::Node::name);
|
||||
ASSERT_NE(module, trace.graphs.front().nodes.end());
|
||||
const auto child_prepare = std::ranges::find(
|
||||
trace.graphs.front().nodes, "prepare.samples",
|
||||
&aethera::Taskflow_Graph_Trace::Node::name);
|
||||
ASSERT_NE(child_prepare, trace.graphs.front().nodes.end());
|
||||
EXPECT_EQ(child_prepare->parent_node_id, module->node_id);
|
||||
EXPECT_EQ(child_prepare->node_id, "test.frame/spectrum/prepare.samples");
|
||||
|
||||
std::unordered_set<std::uint64_t> metadata_ids;
|
||||
for (const auto& node : trace.graphs.front().nodes)
|
||||
metadata_ids.insert(node.native_id);
|
||||
ASSERT_FALSE(trace.tasks.empty());
|
||||
for (const auto& task : trace.tasks)
|
||||
EXPECT_TRUE(metadata_ids.contains(task.native_id));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user