改代码前

This commit is contained in:
2026-08-26 10:55:29 +08:00
parent 65799cfc2a
commit 5ecb12b8f7
59 changed files with 4224 additions and 2458 deletions
+6 -6
View File
@@ -7,9 +7,9 @@ namespace aethera {
namespace detail {
struct Task_Graph_Access;
}
class Task_Graph;
struct Task_Graph;
/* Task_Graph 中单个业务节点的可复制句柄,仅用于静态构图。 */
class Task_Node {
struct Task_Node {
public:
Task_Node();
~Task_Node();
@@ -21,16 +21,16 @@ public:
void precede(const Task_Node& after) const;
Task_Node& describe(std::string key, std::string value);
private:
friend class Task_Graph;
friend struct Task_Graph;
struct Private;
explicit Task_Node(std::shared_ptr<Private> private_data);
std::shared_ptr<Private> d; /* 不透明节点句柄;原生类型只在实现单元可见。 */
std::shared_ptr<Private> d; /* 不透明节点句柄;原生类型只在实现单元可见。 */
};
/*
* Kernel 的业务 DAG。Taskflow 类型、Node 句柄和 Observer 绑定全部留在 Implementation 内。
* 图只允许在未运行时修改;执行期间 add/clear/compose/precede 的行为不受支持。
*/
class Task_Graph {
struct Task_Graph {
public:
explicit Task_Graph(std::string name = {});
~Task_Graph();
@@ -58,6 +58,6 @@ private:
friend struct Task_Node::Private;
friend struct detail::Task_Graph_Access;
struct Private;
std::shared_ptr<Private> d; /* 唯一业务 DAG 定义的不透明所有权。 */
std::shared_ptr<Private> d; /* 唯一业务 DAG 定义的不透明所有权。 */
};
}
@@ -40,7 +40,7 @@ private:
const void* dirty_key;
};
public:
class View {
struct View {
protected:
const Dependency_Graph* dependency_graph;
explicit View(const Dependency_Graph& value) : dependency_graph(&value) {}
@@ -72,11 +72,11 @@ public:
}
template <std::invocable<const Edge&> Callback>
void for_each_edge(Callback&& callback) const {
dependency_graph->for_each_edge(std::forward<Callback>(callback));
dependency_graph->for_each_edge(std::forward < Callback > (callback));
}
};
template <Root_Derived Object>
class Typed_View : public View {
struct Typed_View : public View {
public:
using Object_Type = Object;
using Private_Type = typename Object::Private;
@@ -114,12 +114,12 @@ public:
}
template <std::invocable<const Node&> Callback>
void for_each(Callback&& callback) const {
this->dependency_graph->for_each(std::forward<Callback>(callback));
this->dependency_graph->for_each(std::forward < Callback > (callback));
}
};
// Editor 只操作待提交图;结构是否合法由编辑完成后的拓扑验证统一判断,不在每个底层操作重复检查。
template <Root_Derived Bound_Object>
class Editor : public View {
struct Editor : public View {
private:
Dependency_Graph* edit_dependency_graph;
const void* target_tag;
@@ -334,14 +334,16 @@ private:
}
template <Root_Derived Bound_Object, Root_Derived Object>
static Node::Bind node_bind() {
if constexpr (std::derived_from<Object, Bound_Object> && detail::Bound_Dependency_Object<Object>) {
if constexpr (std::derived_from < Object, Bound_Object > && detail ::Bound_Dependency_Object < Object >)
{
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);
};
}
else {
else
{
return nullptr;
}
}
@@ -604,7 +606,7 @@ public:
}
// 拓扑结果既用于验证无环,也定义上层按照依赖顺序构图和遍历的稳定入口。
std::expected<std::vector<const Node*>, Error> topological_order() const {
enum class Visit {
enum struct Visit {
none,
visiting,
visited
@@ -1,5 +1,4 @@
#pragma once
#include <concepts>
#include <algorithm>
#include <concurrentqueue-1.0.5/concurrentqueue.h>
@@ -13,37 +12,30 @@
#include <type_traits>
#include <utility>
#include <vector>
namespace double_buffer {
/* Def mechanism declaration. Tag selects one independently exchanged MPMC stream. */
template <typename Tag, typename Value>
struct Mpmc_Triple_Buffer {
using Tag_Type = Tag;
using Value_Type = Value;
};
namespace detail {
/*
* The three buffers are queues, not snapshots. Producers append to collecting while
* the renderer owns rendering and observers own query. advance() rotates roles as:
* completed rendering -> query, collecting -> rendering, expired query -> collecting.
*/
template <typename Declaration>
class Mpmc_Triple_Buffer_Storage {
struct Mpmc_Triple_Buffer_Storage {
public:
using Value = typename Declaration::Value_Type;
Mpmc_Triple_Buffer_Storage() = default;
Mpmc_Triple_Buffer_Storage(const Mpmc_Triple_Buffer_Storage&) = delete;
Mpmc_Triple_Buffer_Storage& operator=(const Mpmc_Triple_Buffer_Storage&) = delete;
void submit(Value value) {
std::shared_lock lock(exchange_lock_);
if (!collecting_->enqueue(std::move(value))) throw std::bad_alloc{};
}
void advance() {
std::unique_lock lock(exchange_lock_);
auto* expired_query = query_;
@@ -52,24 +44,20 @@ public:
collecting_ = expired_query;
clear(*collecting_);
}
/*
* Accumulating streams promote the last published version into the current
* rendering version before appending the newly collected batch. Query keeps
* the previous complete version and producers remain isolated in collecting.
*/
void accumulate(std::size_t capacity)
requires std::copy_constructible<Value> {
void accumulate(std::size_t capacity) requires std::copy_constructible<Value> {
accumulate_with([capacity](std::vector<Value>& values) {
if (values.size() <= capacity) return;
values.erase(values.begin(), values.end() -
static_cast<std::ptrdiff_t>(capacity));
});
}
template <typename Predicate>
requires std::copy_constructible<Value> &&
std::predicate<Predicate&, const Value&>
template <typename Predicate> requires std::copy_constructible<Value> &&
std::predicate<Predicate&, const Value&>
void accumulate(Predicate&& retain) {
accumulate_with([&](std::vector<Value>& values) {
std::erase_if(values, [&](const Value& value) {
@@ -77,16 +65,12 @@ public:
});
});
}
template <typename Callback>
requires std::invocable<Callback&, std::span<Value>>
template <typename Callback> requires std::invocable<Callback&, std::span<Value>>
decltype(auto) access_rendering(Callback&& callback) {
std::unique_lock lock(exchange_lock_);
return access(*rendering_, std::forward<Callback>(callback));
}
template <typename Callback>
requires std::invocable<Callback&, std::span<const Value>>
template <typename Callback> requires std::invocable<Callback&, std::span<const Value>>
decltype(auto) access_query(Callback&& callback) const {
std::unique_lock lock(exchange_lock_);
auto adapter = [&](std::span<Value> values) -> decltype(auto) {
@@ -94,37 +78,29 @@ public:
};
return access(*query_, adapter);
}
private:
using Queue = moodycamel::ConcurrentQueue<Value>;
static void clear(Queue& queue) {
Value value;
while (queue.try_dequeue(value)) {}
}
static void restore(Queue& queue, std::vector<Value>& values) {
for (auto& value : values)
if (!queue.enqueue(std::move(value))) throw std::bad_alloc{};
for (auto& value : values) if (!queue.enqueue(std::move(value))) throw std::bad_alloc{};
}
template <typename Mutation>
void accumulate_with(Mutation&& mutate) {
std::unique_lock lock(exchange_lock_);
std::vector<Value> published;
published.reserve(query_->size_approx());
Value value;
while (query_->try_dequeue(value))
published.push_back(std::move(value));
while (query_->try_dequeue(value)) published.push_back(std::move(value));
std::vector<Value> accumulated{published.begin(), published.end()};
restore(*query_, published);
accumulated.reserve(accumulated.size() + rendering_->size_approx());
while (rendering_->try_dequeue(value))
accumulated.push_back(std::move(value));
while (rendering_->try_dequeue(value)) accumulated.push_back(std::move(value));
std::invoke(std::forward<Mutation>(mutate), accumulated);
restore(*rendering_, accumulated);
}
template <typename Callback>
static decltype(auto) access(Queue& queue, Callback&& callback) {
std::vector<Value> values;
@@ -143,21 +119,18 @@ private:
return result;
}
}
mutable std::shared_mutex exchange_lock_; /* Serializes role exchange and stable role inspection. */
mutable Queue first_{}; /* First physical MPMC queue; role changes only under exchange_lock_. */
mutable Queue second_{}; /* Second physical MPMC queue; role changes only under exchange_lock_. */
mutable Queue third_{}; /* Third physical MPMC queue; role changes only under exchange_lock_. */
Queue* collecting_{&first_}; /* Concurrent producer destination until the next exchange. */
Queue* rendering_{&second_}; /* Immutable-to-producers queue consumed by the renderer. */
mutable Queue* query_{&third_}; /* Last completed render input exposed to observers. */
mutable Queue first_{}; /* First physical MPMC queue; role changes only under exchange_lock_. */
mutable Queue second_{}; /* Second physical MPMC queue; role changes only under exchange_lock_. */
mutable Queue third_{}; /* Third physical MPMC queue; role changes only under exchange_lock_. */
Queue* collecting_{&first_}; /* Concurrent producer destination until the next exchange. */
Queue* rendering_{&second_}; /* Immutable-to-producers queue consumed by the renderer. */
mutable Queue* query_{&third_}; /* Last completed render input exposed to observers. */
};
template <typename Tuple>
class Mpmc_Triple_Buffer_Storage_Set;
struct Mpmc_Triple_Buffer_Storage_Set;
template <typename... Declarations>
class Mpmc_Triple_Buffer_Storage_Set<std::tuple<Declarations...>> {
struct Mpmc_Triple_Buffer_Storage_Set<std::tuple<Declarations...>> {
template <typename Tag, typename First, typename... Rest>
static consteval std::size_t index_of() {
if constexpr (std::same_as<Tag, typename First::Tag_Type>) return 0;
@@ -167,17 +140,14 @@ public:
template <typename Tag>
auto& get() noexcept {
constexpr std::size_t index = index_of<Tag, Declarations...>();
return std::get<index>(storage_);
return std::get < index > (storage_);
}
template <typename Tag>
const auto& get() const noexcept {
return const_cast<Mpmc_Triple_Buffer_Storage_Set*>(this)->template get<Tag>();
}
private:
std::tuple<Mpmc_Triple_Buffer_Storage<Declarations>...> storage_;
};
}
}
+41 -32
View File
@@ -53,7 +53,7 @@ public:
}
};
namespace detail {
class Pmr_Resource : public std::pmr::memory_resource {
struct Pmr_Resource : public std::pmr::memory_resource {
private:
using Resource = std::pmr::memory_resource;
struct Header {
@@ -115,8 +115,8 @@ public:
Value* current;
Double_Buffer() : Double_Buffer(std::allocator_arg, allocator_type{std::pmr::get_default_resource()}) {}
Double_Buffer(std::allocator_arg_t, const allocator_type& allocator) : buf(std::allocator_arg, allocator),
pending(&std::get<0>(buf)),
current(&std::get<1>(buf)) {}
pending(&std::get < 0 > (buf)),
current(&std::get < 1 > (buf)) {}
void advance() {
std::swap(pending, current);
}
@@ -252,7 +252,7 @@ template <typename State_T>
struct State_Layers<State_T, std::void_t<typename State_T::Tag_Type, typename State_T::Prev_State>> {
using Type = decltype(std::tuple_cat(
std::declval<std::tuple<State_T>>(),
std::declval<typename State_Layers<typename State_T::Prev_State>::Type>()
std::declval < typename State_Layers<typename State_T::Prev_State>::Type > ()
));
};
template <typename State_T>
@@ -263,7 +263,7 @@ struct Prop_Layers {
};
template <typename Prop_T>
struct Prop_Layers<Prop_T, std::void_t<typename Prop_T::Tag_Type, typename Prop_T::Prev_Prop>> {
using Type = decltype(std::tuple_cat(std::declval<std::tuple<Prop_T>>(), std::declval<typename Prop_Layers<typename Prop_T::Prev_Prop>::Type>()));
using Type = decltype(std::tuple_cat(std::declval<std::tuple<Prop_T>>(), std::declval < typename Prop_Layers<typename Prop_T::Prev_Prop>::Type > ()));
};
template <typename Prop_T>
using Prop_Layers_T = typename Prop_Layers<Prop_T>::Type;
@@ -291,9 +291,13 @@ concept Prop_Member = requires {
requires requires { typename Member_Pointer_Traits<decltype(Member)>::Owner_Type::Prop_Tag; } || Tagged_Prop<typename Member_Pointer_Traits<decltype(Member)>::Owner_Type>;
};
template <typename Owner, typename = void>
struct Prop_Owner_Tag { using Type = typename Owner::Tag_Type; };
struct Prop_Owner_Tag {
using Type = typename Owner::Tag_Type;
};
template <typename Owner>
struct Prop_Owner_Tag<Owner, std::void_t<typename Owner::Prop_Tag>> { using Type = typename Owner::Prop_Tag; };
struct Prop_Owner_Tag<Owner, std::void_t<typename Owner::Prop_Tag>> {
using Type = typename Owner::Prop_Tag;
};
template <auto Member>
using Prop_Member_Tag = typename Prop_Owner_Tag<typename Member_Pointer_Traits<decltype(Member)>::Owner_Type>::Type;
template <typename Value, auto Member, typename State>
@@ -330,9 +334,9 @@ template <typename Tuple>
struct Is_Dependency_Graph_List : std::false_type {};
template <typename... Dependency_Graph_Types>
struct Is_Dependency_Graph_List<std::tuple<Dependency_Graph_Types...>> : Tagged_List_Check<
(Dependency_Graph_Mechanism<Dependency_Graph_Types> && ...) && (std::derived_from<typename Dependency_Graph_Types::Object_Type, Root> && ...),
Dependency_Graph_Types...
> {};
(Dependency_Graph_Mechanism<Dependency_Graph_Types> && ...) && (std::derived_from < typename Dependency_Graph_Types::Object_Type, Root > &&...),
Dependency_Graph_Types...
> {};
template <typename Tuple>
concept Buffer_List = Is_Buffer_List<Tuple>::value;
template <typename Tuple>
@@ -418,10 +422,10 @@ concept State_Chain_Matches = State_Chain<State_T> && State_List<Tuple> && [] {
if constexpr (std::tuple_size_v<Layers> != std::tuple_size_v<Tuple>) return false;
else {
return []<std::size_t... I>(std::index_sequence<I...>) {
return (std::same_as<
typename std::tuple_element_t<I, Layers>::Tag_Type,
typename std::tuple_element_t<std::tuple_size_v<Tuple> - I - 1, Tuple>::Tag_Type
> && ...);
return (std::same_as <
typename std::tuple_element_t < I, Layers > ::Tag_Type,
typename std::tuple_element_t < std::tuple_size_v<Tuple> - I - 1, Tuple > ::Tag_Type
> && ...);
}(std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}
}();
@@ -470,7 +474,9 @@ template <typename Tuple>
struct State_Callback_Tuple;
template <typename... Layers>
struct State_Callback_Tuple<std::tuple<Layers...>> {
using Type = std::tuple<std::function<void(const Layers&)>...>;
using Type = std::tuple<std::function < void(const Layers &)>
...
>;
};
template <typename State_T>
struct State_Callback_Storage {
@@ -487,17 +493,20 @@ public:
void set(Callback&& callback) {
constexpr auto value_index = index<Tag>();
using Layer = std::tuple_element_t<value_index, Layers>;
std::get<value_index>(callbacks) = std::function<void(const Layer&)>{std::forward<Callback>(callback)};
std::get < value_index > (callbacks) = std::function < void(const Layer &) >
{
std::forward<Callback>(callback)
};
}
template <State_Tag_In<Layers> Tag>
void clear() {
std::get<index<Tag>()>(callbacks) = {};
std::get < index<Tag>() > (callbacks) = {};
}
template <State_Tag_In<Layers> Tag>
void notify(const State_T& state) {
constexpr auto value_index = index<Tag>();
using Layer = std::tuple_element_t<value_index, Layers>;
auto& callback = std::get<value_index>(callbacks);
auto& callback = std::get < value_index > (callbacks);
if (callback) callback(static_cast<const Layer&>(state));
}
};
@@ -536,11 +545,11 @@ public:
Buffer_Storage(std::allocator_arg_t, const allocator_type& allocator) : buffers(std::allocator_arg, allocator) {}
template <Buffer_Tag_In<std::tuple<Buffers...>> Tag>
auto& get() {
return std::get<index<Tag>()>(buffers);
return std::get < index<Tag>() > (buffers);
}
template <Buffer_Tag_In<std::tuple<Buffers...>> Tag>
const auto& get() const {
return std::get<index<Tag>()>(buffers);
return std::get < index<Tag>() > (buffers);
}
void advance() {
std::apply(
@@ -557,11 +566,11 @@ 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 初值。 */
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);
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...>) {
@@ -569,12 +578,12 @@ public:
[&] {
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.pending = std::get < I > (values);
*target.current = *target.pending;
}(),
...
);
}(std::index_sequence_for<Buffers...>{});
}(std::index_sequence_for < Buffers...>{});
}
};
}
@@ -583,12 +592,12 @@ public:
* State_Type 为 const 时 get<Tag>() 返回只读层,否则返回可写层;视图不暴露 pending/current 缓冲角色。
*/
template <typename State_Type> requires detail::State_Chain<std::remove_const_t<State_Type>>
class State_Access {
struct State_Access {
private:
using Value_Type = std::remove_const_t<State_Type>;
State_Type* state;
template <typename Other_State_Type> requires detail::State_Chain<std::remove_const_t<Other_State_Type>>
friend class State_Access;
friend struct State_Access;
public:
explicit State_Access(State_Type& value) noexcept : state(&value) {}
template <typename Source_State_Type> requires std::convertible_to<Source_State_Type*, State_Type*>
@@ -604,12 +613,12 @@ template <typename State_Type> requires detail::State_Chain<std::remove_const_t<
State_Access(State_Type&) -> State_Access<State_Type>;
/* Prop_Access 按定义层的 Base_Tag 选择 PropProp 与 State 的标签空间彼此隔离。 */
template <typename Prop_Type> requires detail::Prop_Chain<std::remove_const_t<Prop_Type>>
class Prop_Access {
struct Prop_Access {
private:
using Value_Type = std::remove_const_t<Prop_Type>;
Prop_Type* prop;
template <typename Other_Prop_Type> requires detail::Prop_Chain<std::remove_const_t<Other_Prop_Type>>
friend class Prop_Access;
friend struct Prop_Access;
public:
explicit Prop_Access(Prop_Type& value) noexcept : prop(&value) {}
template <typename Source_Prop_Type> requires std::convertible_to<Source_Prop_Type*, Prop_Type*>
@@ -625,11 +634,11 @@ template <typename Prop_Type> requires detail::Prop_Chain<std::remove_const_t<Pr
Prop_Access(Prop_Type&) -> Prop_Access<Prop_Type>;
/* Private_Access 使用同一个定义层 Base_Tag 选择 Private;该标签空间不与 Prop/State 混用。 */
template <typename Private_Type>
class Private_Access {
struct Private_Access {
private:
Private_Type* private_data;
template <typename Other_Private_Type>
friend class Private_Access;
friend struct Private_Access;
public:
explicit Private_Access(Private_Type& value) noexcept : private_data(&value) {}
template <typename Source_Private_Type> requires std::convertible_to<Source_Private_Type*, Private_Type*>
@@ -684,7 +693,7 @@ struct Dependency_Graph_Storage;
template <typename Tuple>
struct Dependency_Graph_Build_Storage;
}
enum class Dependency_Graph_Error {
enum struct Dependency_Graph_Error {
self_dependency,
missing_dependency,
cycle
@@ -716,7 +725,7 @@ private:
std::pmr::vector<const Dependency_Graph*> dependency_graphs;
std::pmr::vector<const void*> dirty_tags;
protected:
Private* d{}; /* Builder::build() 挂接的最终 Private;由 Root 析构释放。 */
Private* d{}; /* Builder::build() 挂接的最终 Private;由 Root 析构释放。 */
private:
void set_pmr_resource(Pmr pmr) noexcept {
pmr_resource.set_resource(pmr.resource());
+30 -30
View File
@@ -6,7 +6,7 @@
#include <cstdint>
#include <type_traits>
namespace aethera {
enum class Event_Type : std::uint8_t {
enum struct Event_Type : std::uint8_t {
resize,
show,
hide,
@@ -19,21 +19,21 @@ enum class Event_Type : std::uint8_t {
key_release
};
inline constexpr std::size_t event_type_count =
static_cast<std::size_t>(Event_Type::key_release) + 1;
enum class Event_Statistic : std::uint8_t {
static_cast<std::size_t>(Event_Type::key_release) + 1;
enum struct Event_Statistic : std::uint8_t {
queue_wait_ms,
dispatch_ms,
total_ms,
count
};
inline constexpr std::size_t event_statistic_count =
static_cast<std::size_t>(Event_Statistic::count);
static_cast<std::size_t>(Event_Statistic::count);
struct Event_Statistics_State {
std::array<std::array<Statistic_State, event_statistic_count>,
event_type_count> values{}; /* 按事件业务类型发布的定长延迟统计结果。 */
event_type_count> values{}; /* 按事件业务类型发布的定长延迟统计结果。 */
bool operator==(const Event_Statistics_State&) const = default;
};
class Event_Statistics_Accumulator final {
struct Event_Statistics_Accumulator final {
public:
explicit Event_Statistics_Accumulator(std::size_t capacity = 600);
void submit(Event_Type type, std::uint64_t created_steady_ns,
@@ -43,8 +43,8 @@ public:
void reset() noexcept;
private:
std::array<std::array<Sliding_Statistics, event_statistic_count>,
event_type_count> values_; /* Scene 单帧准入域内增量维护的统计器。 */
Event_Statistics_State state_{}; /* 下一次 Scene State 交换时发布的结果。 */
event_type_count> values_; /* Scene 单帧准入域内增量维护的统计器。 */
Event_Statistics_State state_{}; /* 下一次 Scene State 交换时发布的结果。 */
};
/* 所有输入事件的公共业务基类。 */
struct Event {
@@ -61,13 +61,13 @@ struct Event {
void mark_dispatch_started(std::uint64_t frame_sequence) const noexcept;
void mark_dispatch_completed() const noexcept;
[[nodiscard]] Dispatch_Timing dispatch_timing() const noexcept;
Event_Type type; /* 事件种类;构造后保持不变。 */
Event_Type type; /* 事件种类;构造后保持不变。 */
private:
friend struct Scene;
void observe_with(Event_Statistics_Accumulator* accumulator) noexcept;
std::uint64_t created_steady_ns_{}; /* 事件对象进入 Scene 前的内核单调时刻。 */
Event_Statistics_Accumulator* statistics_{}; /* 所属 Scene 的统计器;不拥有且不得跨 Scene。 */
mutable bool accepted{}; /* 处理链是否已经消费事件。 */
std::uint64_t created_steady_ns_{}; /* 事件对象进入 Scene 前的内核单调时刻。 */
Event_Statistics_Accumulator* statistics_{}; /* 所属 Scene 的统计器;不拥有且不得跨 Scene。 */
mutable bool accepted{}; /* 处理链是否已经消费事件。 */
mutable std::atomic_uint64_t dispatch_frame_sequence_{};
mutable std::atomic_uint64_t dispatch_started_steady_ns_{};
mutable std::atomic_uint64_t dispatch_completed_steady_ns_{};
@@ -84,9 +84,9 @@ 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 };
enum struct Mouse_Button : std::uint8_t { none, left, right, middle };
using Mouse_Button_Mask = std::uint8_t;
enum class Keyboard_Modifier : std::uint8_t {
enum struct Keyboard_Modifier : std::uint8_t {
none = 0,
control = 1 << 0,
shift = 1 << 1,
@@ -115,11 +115,11 @@ struct Wheel_Event_Capability {
template <Event_Point Point>
struct Basic_Pointer_Event : Event, Pointer_Event_Capability {
explicit Basic_Pointer_Event(Event_Type value = Event_Type::pointer_move);
Point position{}; /* 事件接收对象局部坐标。 */
Point global_position{}; /* 全局窗口坐标。 */
Mouse_Button button{Mouse_Button::none}; /* 本次按下或释放的按键。 */
Mouse_Button_Mask buttons{}; /* 事件发生时保持按下的按键集合。 */
Keyboard_Modifier modifiers{Keyboard_Modifier::none}; /* 事件发生时的键盘修饰键集合。 */
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;
[[nodiscard]] double position_y() const noexcept override;
[[nodiscard]] Mouse_Button pointer_button() const noexcept override;
@@ -130,10 +130,10 @@ struct Basic_Pointer_Event : Event, Pointer_Event_Capability {
template <Event_Point Point>
struct Basic_Wheel_Event : Basic_Pointer_Event<Point>, Wheel_Event_Capability {
Basic_Wheel_Event();
double angle_delta_x{}; /* 水平方向滚轮角度增量。 */
double angle_delta_y{}; /* 垂直方向滚轮角度增量。 */
double pixel_delta_x{}; /* 水平方向高精度像素增量。 */
double pixel_delta_y{}; /* 垂直方向高精度像素增量。 */
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;
[[nodiscard]] double pixel_delta_y_value() const noexcept override;
[[nodiscard]] double angle_delta_x_value() const noexcept override;
@@ -143,10 +143,10 @@ struct Basic_Wheel_Event : Basic_Pointer_Event<Point>, Wheel_Event_Capability {
template <Event_Size Size>
struct Basic_Resize_Event : Event {
Basic_Resize_Event();
Size old_size{}; /* 调整前尺寸。 */
Size new_size{}; /* 调整后尺寸。 */
Size old_size{}; /* 调整前尺寸。 */
Size new_size{}; /* 调整后尺寸。 */
};
enum class Key : std::uint16_t {
enum struct Key : std::uint16_t {
unknown,
escape,
enter,
@@ -162,10 +162,10 @@ enum class Key : std::uint16_t {
/* 键盘按下或释放事件。 */
struct Key_Event : Event {
explicit Key_Event(Event_Type value);
Key key{Key::unknown}; /* 标准化按键。 */
std::uint32_t native_key{}; /* 平台原生按键编码。 */
Keyboard_Modifier modifiers{Keyboard_Modifier::none}; /* 事件发生时的修饰键集合。 */
bool auto_repeat{}; /* 是否由系统自动重复产生。 */
Key key{Key::unknown}; /* 标准化按键。 */
std::uint32_t native_key{}; /* 平台原生按键编码。 */
Keyboard_Modifier modifiers{Keyboard_Modifier::none}; /* 事件发生时的修饰键集合。 */
bool auto_repeat{}; /* 是否由系统自动重复产生。 */
};
}
#include "event.ipp"
+49 -47
View File
@@ -5,10 +5,12 @@
#include <string>
#include <vector>
namespace aethera {
namespace detail { struct Taskflow_Frame_Access; }
enum class Frame_Dimension : std::uint8_t;
namespace detail {
struct Taskflow_Frame_Access;
}
enum struct Frame_Dimension : std::uint8_t;
struct Frame_Statistics_Sample;
enum class Frame_Trace_Marker : std::uint8_t {
enum struct Frame_Trace_Marker : std::uint8_t {
created,
scene_render_requested,
scene_render_started,
@@ -41,7 +43,7 @@ enum class Frame_Trace_Marker : std::uint8_t {
frame_ready,
count
};
enum class Frame_Trace_Measurement : std::uint8_t {
enum struct Frame_Trace_Measurement : std::uint8_t {
plot_tick_queue_ns,
plot_update_ns,
plot_publish_ns,
@@ -59,62 +61,62 @@ enum class Frame_Trace_Measurement : std::uint8_t {
};
struct Frame_Identity {
friend bool operator==(const Frame_Identity&, const Frame_Identity&) = default;
std::uint64_t sequence{}; /* 外部帧管理器分配的单调帧序号。 */
std::uint64_t correlation_id{}; /* 与调用方请求关联的标识;零值表示未关联。 */
std::uint64_t sequence{}; /* 外部帧管理器分配的单调帧序号。 */
std::uint64_t correlation_id{}; /* 与调用方请求关联的标识;零值表示未关联。 */
};
struct Frame_Trace_Point {
Frame_Trace_Marker marker{}; /* 本时间点在端到端帧流水线中的固定语义。 */
std::uint64_t elapsed_ns{}; /* 相对 Frame 创建时刻的单调时钟偏移,单位为纳秒。 */
Frame_Trace_Marker marker{}; /* 本时间点在端到端帧流水线中的固定语义。 */
std::uint64_t elapsed_ns{}; /* 相对 Frame 创建时刻的单调时钟偏移,单位为纳秒。 */
};
struct Frame_Trace_Value {
Frame_Trace_Measurement measurement{}; /* 无法表达为公共时间点的原始后端测量类型。 */
std::uint64_t value_ns{}; /* 后端直接记录的持续时间,单位为纳秒。 */
Frame_Trace_Measurement measurement{}; /* 无法表达为公共时间点的原始后端测量类型。 */
std::uint64_t value_ns{}; /* 后端直接记录的持续时间,单位为纳秒。 */
};
struct Taskflow_Graph_Trace {
std::string stage{}; /* 本帧中执行该原生 Taskflow 的业务阶段。 */
std::string taskflow_name{}; /* Taskflow 原生图名称。 */
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::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<std::pair<std::string, std::string>> attributes{};
};
std::vector<Node> nodes{}; /* DAG 构造时登记的节点与依赖元信息。 */
double submitted_ms{}; /* 相对帧创建时刻的 run 提交时间。 */
double finished_ms{}; /* 同步返回或异步 topology 完成时间。 */
bool completed{}; /* 对应 topology 是否已经结束。 */
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 entered_ms{}; /* Observer on_entry 开始,即 Executor 真正选中任务的时间。 */
double started_ms{}; /* Observer on_entry 返回前,即任务体即将执行的时间。 */
double finished_ms{}; /* Observer on_exit 进入,即任务体已经结束的时间。 */
double completed_ms{}; /* Observer on_exit 与按帧追踪写入全部结束的时间。 */
double duration_ms{}; /* 仅任务体 started 到 finished 的持续时间。 */
double cpu_duration_ms{}; /* 任务体在当前 worker 线程上实际消耗的 CPU 时间。 */
double observer_entry_ms{}; /* on_entry 诊断本身的耗时。 */
double observer_exit_ms{}; /* on_exit 诊断与按帧追踪写入的耗时。 */
double observer_entry_cpu_ms{}; /* on_entry 诊断实际消耗的 worker CPU 时间。 */
double observer_exit_cpu_ms{}; /* on_exit 诊断实际消耗的 worker CPU 时间。 */
double ready_ms{}; /* 前驱完成或根 run 提交后的估算就绪时间。 */
double queue_wait_ms{}; /* ready 到 entered 的估算 Executor 排队时间。 */
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 entered_ms{}; /* Observer on_entry 开始,即 Executor 真正选中任务的时间。 */
double started_ms{}; /* Observer on_entry 返回前,即任务体即将执行的时间。 */
double finished_ms{}; /* Observer on_exit 进入,即任务体已经结束的时间。 */
double completed_ms{}; /* Observer on_exit 与按帧追踪写入全部结束的时间。 */
double duration_ms{}; /* 仅任务体 started 到 finished 的持续时间。 */
double cpu_duration_ms{}; /* 任务体在当前 worker 线程上实际消耗的 CPU 时间。 */
double observer_entry_ms{}; /* on_entry 诊断本身的耗时。 */
double observer_exit_ms{}; /* on_exit 诊断与按帧追踪写入的耗时。 */
double observer_entry_cpu_ms{}; /* on_entry 诊断实际消耗的 worker CPU 时间。 */
double observer_exit_cpu_ms{}; /* on_exit 诊断实际消耗的 worker CPU 时间。 */
double ready_ms{}; /* 前驱完成或根 run 提交后的估算就绪时间。 */
double queue_wait_ms{}; /* ready 到 entered 的估算 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<Frame_Trace_Point> markers{}; /* 与该帧 DAG 共用时间原点的原始流水线时间点。 */
std::vector<Taskflow_Graph_Trace> graphs{}; /* 本帧主动执行的业务 DAG 元信息。 */
std::vector<Taskflow_Task_Trace> tasks{}; /* 本帧窗口内原生 Observer 完成的任务执行。 */
Frame_Identity identity{}; /* 该执行图所属逻辑渲染帧。 */
std::uint64_t created_time_unix_ns{}; /* 帧创建 Unix 时间,单位纳秒。 */
std::size_t worker_count{}; /* 捕获时全局 Executor 的 worker 数。 */
std::vector<Frame_Trace_Point> markers{}; /* 与该帧 DAG 共用时间原点的原始流水线时间点。 */
std::vector<Taskflow_Graph_Trace> graphs{}; /* 本帧主动执行的业务 DAG 元信息。 */
std::vector<Taskflow_Task_Trace> tasks{}; /* 本帧窗口内原生 Observer 完成的任务执行。 */
};
class Render_Frame : public double_buffer::Pinned {
struct Render_Frame : public double_buffer::Pinned {
public:
explicit Render_Frame(Frame_Identity identity);
virtual ~Render_Frame();
@@ -132,6 +134,6 @@ protected:
private:
struct Private;
friend struct detail::Taskflow_Frame_Access;
std::unique_ptr<Private> d; /* 帧身份、时钟原点与原子诊断槽位的唯一所有权。 */
std::unique_ptr<Private> d; /* 帧身份、时钟原点与原子诊断槽位的唯一所有权。 */
};
}
+11 -20
View File
@@ -6,11 +6,9 @@
#include <cstddef>
#include <cstdint>
#include <vector>
namespace aethera {
enum class Frame_Dimension : std::uint8_t { two_dimensional, three_dimensional };
enum class Frame_Statistic : std::uint8_t {
enum struct Frame_Dimension : std::uint8_t { two_dimensional, three_dimensional };
enum struct Frame_Statistic : std::uint8_t {
plot_tick_queue_ms,
plot_update_ms,
plot_publish_ms,
@@ -66,17 +64,13 @@ enum class Frame_Statistic : std::uint8_t {
frame_interval_ms,
count
};
inline constexpr std::size_t frame_statistic_count =
static_cast<std::size_t>(Frame_Statistic::count);
static_cast<std::size_t>(Frame_Statistic::count);
struct Frame_Statistics_Sample {
std::array<double, frame_statistic_count> values{};
std::bitset<frame_statistic_count> present{};
void set(Frame_Statistic statistic, double value) noexcept;
};
struct Statistic_State {
std::size_t count{};
double latest{};
@@ -90,18 +84,16 @@ struct Statistic_State {
double p99{};
bool operator==(const Statistic_State&) const = default;
};
class Sliding_Statistics final {
struct Sliding_Statistics final {
public:
Sliding_Statistics();
explicit Sliding_Statistics(std::size_t capacity);
[[nodiscard]] Statistic_State submit(double value);
[[nodiscard]] const Statistic_State& state() const noexcept;
void reset() noexcept;
private:
/* P² 只维护五个标记点,分位数是从 reset 起的在线估计,不保存样本。 */
class Quantile_Estimator final {
struct Quantile_Estimator final {
public:
explicit Quantile_Estimator(double probability) noexcept;
void submit(double value) noexcept;
@@ -116,9 +108,8 @@ private:
std::array<double, 5> increments_{};
std::size_t count_{};
};
/* 预分配单调队列,O(1) 摊还维护精确滑动窗口极值。 */
class Extremum_Queue final {
struct Extremum_Queue final {
public:
Extremum_Queue(std::size_t capacity, bool minimum);
void submit(double value, std::uint64_t sequence,
@@ -126,13 +117,15 @@ private:
[[nodiscard]] double value() const noexcept;
void reset() noexcept;
private:
struct Node { double value{}; std::uint64_t sequence{}; };
struct Node {
double value{};
std::uint64_t sequence{};
};
std::vector<Node> nodes_;
std::size_t head_{};
std::size_t tail_{};
bool minimum_{};
};
/* 原始值和截尾值只为精确滑动均值/方差服务,构造后不再分配。 */
std::vector<double> values_;
std::vector<double> trimmed_values_;
@@ -151,7 +144,6 @@ private:
Quantile_Estimator p99_{0.99};
Statistic_State state_{};
};
struct Frame_Statistics_State {
/* 可直接通过 Scene State 双缓冲发布的定长结果;不含统计器内部样本。 */
std::array<Statistic_State, frame_statistic_count> values{};
@@ -160,8 +152,7 @@ struct Frame_Statistics_State {
std::uint64_t dropped_sequences{};
bool operator==(const Frame_Statistics_State&) const = default;
};
class Frame_Statistics_Accumulator final {
struct Frame_Statistics_Accumulator final {
public:
explicit Frame_Statistics_Accumulator(std::size_t capacity = 600);
[[nodiscard]] const Frame_Statistics_State& submit(
+1 -13
View File
@@ -1,43 +1,34 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
namespace aethera::plot {
using Axis_Coordinate = double;
enum class Axis_Scale : std::uint8_t {
enum struct Axis_Scale : std::uint8_t {
linear,
logarithmic,
time
};
struct Axis_Range {
Axis_Coordinate origin{};
Axis_Coordinate target{};
[[nodiscard]] Axis_Coordinate size() const noexcept;
[[nodiscard]] Axis_Coordinate length() const noexcept;
[[nodiscard]] Axis_Coordinate center() const noexcept;
[[nodiscard]] bool contains(Axis_Coordinate coordinate) const noexcept;
bool operator==(const Axis_Range&) const = default;
};
struct Axis_Point {
Axis_Coordinate horizontal{};
Axis_Coordinate vertical{};
bool operator==(const Axis_Point&) const = default;
};
struct Axis_Rectangle {
Axis_Range horizontal{};
Axis_Range vertical{};
bool operator==(const Axis_Rectangle&) const = default;
};
struct Axis_Descriptor {
Axis_Range range{};
Axis_Scale scale{Axis_Scale::linear};
@@ -50,15 +41,12 @@ struct Axis_Descriptor {
bool labels_visible{true};
bool operator==(const Axis_Descriptor&) const = default;
};
struct Axis_Tick {
Axis_Coordinate coordinate{};
std::string label{};
bool operator==(const Axis_Tick&) const = default;
};
[[nodiscard]] Axis_Coordinate nice_tick_step(Axis_Range range,
std::size_t target_tick_count = 6);
[[nodiscard]] std::vector<Axis_Tick> axis_ticks(const Axis_Descriptor& axis);
} // namespace aethera::plot
+94 -97
View File
@@ -29,17 +29,14 @@ struct Task_Name_Registry {
std::unordered_map<std::uint64_t,
std::shared_ptr<const std::string>> names;
};
Task_Name_Registry& task_name_registry() {
static Task_Name_Registry value;
return value;
}
tf::Taskflow& native_taskflow(Task_Graph& graph) noexcept {
return *static_cast<tf::Taskflow*>(
detail::Task_Graph_Access::native_storage(graph));
}
#if defined(_WIN32)
std::uint64_t thread_cpu_ns(HANDLE thread) noexcept {
FILETIME created{}, exited{}, kernel{}, user{};
@@ -54,14 +51,13 @@ std::uint64_t thread_cpu_ns(HANDLE thread) noexcept {
};
return (ticks(kernel) + ticks(user)) * 100ULL;
}
std::uint64_t thread_cpu_cycles(HANDLE thread) noexcept {
ULONG64 cycles{};
return thread && QueryThreadCycleTime(thread, &cycles)
? static_cast<std::uint64_t>(cycles) : 0;
? static_cast<std::uint64_t>(cycles)
: 0;
}
#endif
std::uint64_t current_thread_cpu_ns() noexcept {
#if defined(_WIN32)
return thread_cpu_ns(GetCurrentThread());
@@ -69,13 +65,12 @@ std::uint64_t current_thread_cpu_ns() noexcept {
timespec value{};
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &value) != 0) return 0;
return static_cast<std::uint64_t>(value.tv_sec) * 1'000'000'000ULL +
static_cast<std::uint64_t>(value.tv_nsec);
static_cast<std::uint64_t>(value.tv_nsec);
#else
return 0;
#endif
}
class Task_Observer : public tf::ObserverInterface {
struct Task_Observer : public tf::ObserverInterface {
private:
using Clock = std::chrono::steady_clock;
struct Task_Statistics {
@@ -99,9 +94,9 @@ private:
std::atomic_uint64_t active_segment_started_ns{};
std::atomic_uint64_t active_segment_cpu_started_ns{};
#if defined(_WIN32)
std::atomic_uintptr_t native_thread_handle{}; /* Watchdog 只读的真实 Worker 线程句柄。 */
std::atomic_uint64_t active_cpu_cycles{}; /* 连续片段最近一次采样的 Worker CPU 周期。 */
std::atomic_uint64_t active_cpu_progress_ns{}; /* CPU 周期最后前进的墙钟时刻。 */
std::atomic_uintptr_t native_thread_handle{}; /* Watchdog 只读的真实 Worker 线程句柄。 */
std::atomic_uint64_t active_cpu_cycles{}; /* 连续片段最近一次采样的 Worker CPU 周期。 */
std::atomic_uint64_t active_cpu_progress_ns{}; /* CPU 周期最后前进的墙钟时刻。 */
#endif
std::atomic<tf::TaskType> active_task_type{tf::TaskType::UNDEFINED};
std::atomic_bool active_task_reported{};
@@ -122,18 +117,18 @@ private:
std::atomic<std::shared_ptr<const std::string>> longest_task_name{};
};
struct Start_Record {
Clock::time_point entered{}; /* Observer on_entry 进入时间。 */
Clock::time_point started{}; /* on_entry 完成、任务体即将执行的时间。 */
Clock::time_point segment_started{}; /* 当前连续独占 Worker 片段的起点。 */
std::uint64_t maximum_segment_ns{}; /* 已结束连续独占片段的最大墙钟。 */
std::uint64_t cpu_entered_ns{}; /* on_entry 进入时的 worker CPU 时间。 */
std::uint64_t cpu_started_ns{}; /* 任务体开始前的 worker CPU 时间。 */
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 返回外层任务时恢复其原生类型。 */
bool cooperatively_suspended{}; /* 外层任务是否主动让出 Worker 执行子图。 */
Clock::time_point entered{}; /* Observer on_entry 进入时间。 */
Clock::time_point started{}; /* on_entry 完成、任务体即将执行的时间。 */
Clock::time_point segment_started{}; /* 当前连续独占 Worker 片段的起点。 */
std::uint64_t maximum_segment_ns{}; /* 已结束连续独占片段的最大墙钟。 */
std::uint64_t cpu_entered_ns{}; /* on_entry 进入时的 worker CPU 时间。 */
std::uint64_t cpu_started_ns{}; /* 任务体开始前的 worker CPU 时间。 */
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 返回外层任务时恢复其原生类型。 */
bool cooperatively_suspended{}; /* 外层任务是否主动让出 Worker 执行子图。 */
};
std::vector<std::vector<Start_Record>> starts;
std::vector<Clock::time_point> worker_busy_starts;
@@ -141,13 +136,13 @@ private:
std::unique_ptr<Worker_Statistics[]> worker_statistics;
std::size_t worker_statistics_count{};
std::atomic<Render_Frame*> trace_frame{};
std::shared_mutex trace_mutex{}; /* 仅按需捕获时保护 Frame* 获取与关闭。 */
std::uint64_t worker_occupation_limit_ns{}; /* 单节点连续非 CPU 等待 Worker 的上限。 */
Task_Overrun_Action worker_overrun_action{}; /* 节点超过占用上限后的处置策略。 */
std::atomic_bool watchdog_stopping{}; /* Watchdog 生命周期停止标志。 */
std::mutex watchdog_mutex{}; /* 仅用于 Watchdog 条件休眠。 */
std::condition_variable watchdog_wake{}; /* Observer 销毁时唤醒 Watchdog。 */
std::thread watchdog_thread{}; /* 不占用 Executor Worker 的超时检查线程。 */
std::shared_mutex trace_mutex{}; /* 仅按需捕获时保护 Frame* 获取与关闭。 */
std::uint64_t worker_occupation_limit_ns{}; /* 单节点连续非 CPU 等待 Worker 的上限。 */
Task_Overrun_Action worker_overrun_action{}; /* 节点超过占用上限后的处置策略。 */
std::atomic_bool watchdog_stopping{}; /* Watchdog 生命周期停止标志。 */
std::mutex watchdog_mutex{}; /* 仅用于 Watchdog 条件休眠。 */
std::condition_variable watchdog_wake{}; /* Observer 销毁时唤醒 Watchdog。 */
std::thread watchdog_thread{}; /* 不占用 Executor Worker 的超时检查线程。 */
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());
}
@@ -196,8 +191,7 @@ private:
state.current_queue_size.load(std::memory_order_relaxed),
state.current_queue_capacity.load(std::memory_order_relaxed));
std::fflush(stderr);
if (worker_overrun_action == Task_Overrun_Action::fast_fail)
std::abort();
if (worker_overrun_action == Task_Overrun_Action::fast_fail) std::abort();
}
void run_watchdog() noexcept {
const auto interval = std::chrono::nanoseconds(
@@ -245,7 +239,7 @@ private:
const auto stalled = wall;
#endif
if (state.active_task_reported.exchange(
true, std::memory_order_acq_rel))
true, std::memory_order_acq_rel))
continue;
report_active_overrun(worker, state, wall, stalled);
}
@@ -297,14 +291,13 @@ private:
worker_statistics[worker].active_task_reported.store(
false, std::memory_order_relaxed);
}
friend class Task_Resource;
friend struct Task_Resource;
public:
Task_Observer(std::chrono::milliseconds worker_occupation_limit,
Task_Overrun_Action worker_overrun_action_value)
: worker_occupation_limit_ns(static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
worker_occupation_limit).count())),
worker_overrun_action(worker_overrun_action_value) {}
Task_Overrun_Action worker_overrun_action_value) : worker_occupation_limit_ns(static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
worker_occupation_limit).count())),
worker_overrun_action(worker_overrun_action_value) {}
~Task_Observer() override {
watchdog_stopping.store(true, std::memory_order_release);
watchdog_wake.notify_one();
@@ -313,7 +306,7 @@ public:
for (std::size_t worker = 0;
worker < worker_statistics_count; ++worker) {
const auto handle = worker_statistics[worker]
.native_thread_handle.exchange(0, std::memory_order_acq_rel);
.native_thread_handle.exchange(0, std::memory_order_acq_rel);
if (handle) CloseHandle(reinterpret_cast<HANDLE>(handle));
}
#endif
@@ -325,7 +318,9 @@ public:
worker_statistics = std::make_unique<Worker_Statistics[]>(workers);
worker_statistics_count = workers;
for (auto& worker : starts) worker.reserve(8);
watchdog_thread = std::thread([this] { run_watchdog(); });
watchdog_thread = std::thread([this] {
run_watchdog();
});
}
void on_entry(tf::WorkerView worker, tf::TaskView task) override {
auto now = Clock::now();
@@ -333,17 +328,17 @@ public:
auto& worker_state = worker_statistics[worker.id()];
#if defined(_WIN32)
if (worker_state.native_thread_handle.load(
std::memory_order_acquire) == 0) {
std::memory_order_acquire) == 0) {
HANDLE duplicated{};
if (DuplicateHandle(
GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &duplicated, 0, FALSE,
DUPLICATE_SAME_ACCESS)) {
GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &duplicated, 0, FALSE,
DUPLICATE_SAME_ACCESS)) {
std::uintptr_t expected{};
if (!worker_state.native_thread_handle.compare_exchange_strong(
expected, reinterpret_cast<std::uintptr_t>(duplicated),
std::memory_order_release,
std::memory_order_relaxed))
expected, reinterpret_cast<std::uintptr_t>(duplicated),
std::memory_order_release,
std::memory_order_relaxed))
CloseHandle(duplicated);
}
}
@@ -366,7 +361,8 @@ public:
worker_starts.push_back(Start_Record{
now, {}, {}, 0, current_thread_cpu_ns(), 0, nullptr,
worker.queue_size(), worker.queue_capacity(),
static_cast<std::uint64_t>(task.hash_value()), task.type(), false});
static_cast<std::uint64_t>(task.hash_value()), task.type(), false
});
auto* frame = trace_frame.load(std::memory_order_acquire);
/*
* on_entry on_exit退
@@ -377,7 +373,7 @@ public:
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()))) {
*frame, static_cast<std::uint64_t>(task.hash_value()))) {
detail::Taskflow_Frame_Access::release_writer(*frame);
frame = nullptr;
}
@@ -415,8 +411,7 @@ public:
task.num_strong_dependencies());
update_max(worker_state.max_weak_dependencies,
task.num_weak_dependencies());
if (!task.name().empty())
worker_state.named_task_count.fetch_add(1, std::memory_order_relaxed);
if (!task.name().empty()) worker_state.named_task_count.fetch_add(1, std::memory_order_relaxed);
update_first(worker_state.first_task_time_ns, clock_ns(now));
worker_starts.back().cpu_started_ns = current_thread_cpu_ns();
worker_starts.back().started = Clock::now();
@@ -456,7 +451,7 @@ public:
auto longest = worker_state.longest_task_time_ns.load(
std::memory_order_relaxed);
if (longest < elapsed && worker_state.longest_task_time_ns.compare_exchange_strong(
longest, elapsed, std::memory_order_relaxed)) {
longest, elapsed, std::memory_order_relaxed)) {
worker_state.longest_task_hash.store(task.hash_value(), std::memory_order_relaxed);
worker_state.longest_task_type.store(task.type(), std::memory_order_relaxed);
worker_state.longest_task_name.store(
@@ -494,7 +489,8 @@ public:
worker_state.busy_time_ns.fetch_add(busy, std::memory_order_relaxed);
const auto cpu_started = worker_cpu_starts[worker.id()];
const auto cpu = cpu_completed_ns >= cpu_started
? cpu_completed_ns - cpu_started : 0;
? cpu_completed_ns - cpu_started
: 0;
worker_state.cpu_time_ns.fetch_add(cpu, 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);
@@ -524,9 +520,9 @@ public:
0, std::memory_order_release);
#if defined(_WIN32)
worker_state.active_cpu_cycles.store(0,
std::memory_order_release);
std::memory_order_release);
worker_state.active_cpu_progress_ns.store(0,
std::memory_order_release);
std::memory_order_release);
#endif
}
else {
@@ -537,8 +533,8 @@ public:
cpu_completed_ns, std::memory_order_release);
#if defined(_WIN32)
const auto native_handle =
worker_state.native_thread_handle.load(
std::memory_order_acquire);
worker_state.native_thread_handle.load(
std::memory_order_acquire);
worker_state.active_cpu_cycles.store(
thread_cpu_cycles(reinterpret_cast<HANDLE>(native_handle)),
std::memory_order_release);
@@ -552,7 +548,7 @@ public:
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);
std::memory_order_relaxed);
}
update_max(worker_state.last_task_time_ns, clock_ns(completed));
}
@@ -596,7 +592,7 @@ public:
state.task_types.assign(tf::TASK_TYPES.size(), {});
for (std::size_t type = 0; type < tf::TASK_TYPES.size(); ++type)
state.task_types[type].name =
std::string(tf::to_string(tf::TASK_TYPES[type]));
std::string(tf::to_string(tf::TASK_TYPES[type]));
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) {
@@ -611,24 +607,27 @@ public:
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;
? 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{};
? 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.cpu_time_ns = source.cpu_time_ns.load(std::memory_order_relaxed);
target.non_cpu_time_ns = target.busy_time_ns > target.cpu_time_ns
? target.busy_time_ns - target.cpu_time_ns : 0;
? target.busy_time_ns - target.cpu_time_ns
: 0;
target.idle_time_ns = state.observed_wall_time_ns > target.busy_time_ns ? state.observed_wall_time_ns - target.busy_time_ns : 0;
auto min = source.min_task_time_ns.load(std::memory_order_relaxed);
target.min_task_time_ns = target.task_count ? min : 0;
target.max_task_time_ns = source.max_task_time_ns.load(std::memory_order_relaxed);
target.utilization = state.observed_wall_time_ns ? static_cast<double>(target.busy_time_ns) * 100.0 / static_cast<double>(state.observed_wall_time_ns) : 0.0;
target.cpu_utilization = state.observed_wall_time_ns
? static_cast<double>(target.cpu_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns)
: 0.0;
? static_cast<double>(target.cpu_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns)
: 0.0;
const auto active_depth = source.active_depth.load(
std::memory_order_relaxed);
state.active_task_count += active_depth;
@@ -645,9 +644,9 @@ public:
state.max_observed_worker_queue_capacity,
target.max_observed_queue_capacity);
state.max_predecessors = std::max(state.max_predecessors,
source.max_predecessors.load(std::memory_order_relaxed));
source.max_predecessors.load(std::memory_order_relaxed));
state.max_successors = std::max(state.max_successors,
source.max_successors.load(std::memory_order_relaxed));
source.max_successors.load(std::memory_order_relaxed));
state.max_strong_dependencies = std::max(
state.max_strong_dependencies,
source.max_strong_dependencies.load(std::memory_order_relaxed));
@@ -659,10 +658,9 @@ public:
state.worker_cpu_time_ns += target.cpu_time_ns;
const auto worker_first = source.first_task_time_ns.load(
std::memory_order_relaxed);
if (worker_first && (!first || worker_first < first))
first = worker_first;
if (worker_first && (!first || worker_first < first)) first = worker_first;
last = std::max(last, source.last_task_time_ns.load(
std::memory_order_relaxed));
std::memory_order_relaxed));
for (std::size_t type = 0; type < tf::TASK_TYPES.size(); ++type) {
const auto& source_type = source.task_types[type];
auto& target_type = state.task_types[type];
@@ -674,7 +672,7 @@ public:
const auto minimum = source_type.min_time_ns.load(
std::memory_order_relaxed);
if (count && (!target_type.min_time_ns ||
minimum < target_type.min_time_ns))
minimum < target_type.min_time_ns))
target_type.min_time_ns = minimum;
target_type.max_time_ns = std::max(
target_type.max_time_ns,
@@ -697,26 +695,31 @@ public:
state.peak_active_worker_count = std::max(
state.peak_active_worker_count, state.active_worker_count);
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;
? 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.worker_cpu_utilization = workers && state.observed_wall_time_ns
? static_cast<double>(state.worker_cpu_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns) /
static_cast<double>(workers) : 0.0;
? static_cast<double>(state.worker_cpu_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns) /
static_cast<double>(workers)
: 0.0;
for (auto& target : state.workers) {
target.idle_time_ns = state.observed_wall_time_ns > target.busy_time_ns
? state.observed_wall_time_ns - target.busy_time_ns : 0;
? state.observed_wall_time_ns - target.busy_time_ns
: 0;
target.utilization = state.observed_wall_time_ns
? static_cast<double>(target.busy_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns) : 0.0;
? static_cast<double>(target.busy_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns)
: 0.0;
target.cpu_utilization = state.observed_wall_time_ns
? static_cast<double>(target.cpu_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns) : 0.0;
? static_cast<double>(target.cpu_time_ns) * 100.0 /
static_cast<double>(state.observed_wall_time_ns)
: 0.0;
}
}
};
class Task_Resource : Pinned {
struct Task_Resource : Pinned {
private:
std::unique_ptr<tf::Executor> executor;
std::shared_ptr<Task_Observer> observer;
@@ -802,8 +805,7 @@ public:
}
observer->resume_worker(worker->id());
}
else
executor->run(native_taskflow(taskflow)).get();
else executor->run(native_taskflow(taskflow)).get();
}
catch (...) {
active_taskflows.fetch_sub(1, std::memory_order_relaxed);
@@ -823,7 +825,7 @@ public:
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)) {}
peak, active, std::memory_order_relaxed)) {}
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);
@@ -879,7 +881,7 @@ public:
std::pmr::memory_resource* memory_resource() const noexcept {
return memory;
}
void set_state_callback(std::function<void(const Task_Runtime_State&)> callback) {
void set_state_callback(std::function<void(const Task_Runtime_State &)> callback) {
ensure_executor();
std::lock_guard guard(state_mutex);
state_callbacks.template set<Task_Runtime_State_Tag>(std::move(callback));
@@ -910,7 +912,6 @@ public:
}
};
}
namespace detail {
void register_taskflow_node(std::uint64_t native_id,
std::string_view name) {
@@ -920,7 +921,6 @@ void register_taskflow_node(std::uint64_t native_id,
registry.names.insert_or_assign(
native_id, std::make_shared<const std::string>(name));
}
std::shared_ptr<const std::string> taskflow_node_name(
std::uint64_t native_id) noexcept {
try {
@@ -933,12 +933,10 @@ std::shared_ptr<const std::string> taskflow_node_name(
return {};
}
}
void corun_taskflow_until(std::function<bool()> predicate) {
Task_Resource::instance().corun_until(std::move(predicate));
}
}
void initialize_runtime(Task_Runtime_Configuration configuration) {
Task_Resource::instance().initialize(std::move(configuration), nullptr);
}
@@ -949,7 +947,7 @@ 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) {
void set_runtime_state_callback_impl(std::function < void(const Task_Runtime_State &) > callback) {
Task_Resource::instance().set_state_callback(std::move(callback));
}
void clear_runtime_state_callback_impl() {
@@ -971,11 +969,10 @@ void run_taskflow(Task_Graph& taskflow, Render_Frame& frame,
}
bool begin_taskflow_trace(Render_Frame& frame) {
return !frame.taskflow_trace_requested() ||
Task_Resource::instance().begin_trace(frame);
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);
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();
+2 -2
View File
@@ -99,7 +99,7 @@ struct Task_Runtime_State : State_Type<Task_Runtime_State_Tag> {
std::vector<Task_Worker_State> workers;
bool operator==(const Task_Runtime_State&) const = default;
};
enum class Task_Overrun_Action : std::uint8_t {
enum struct Task_Overrun_Action : std::uint8_t {
warning,
fast_fail
};
@@ -124,7 +124,7 @@ void schedule_task(std::string name, std::function<void()> task);
[[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);
void set_runtime_state_callback(Callback && callback);
/* 清除全局 Taskflow 运行时状态回调;Tag 目前只接受 Task_Runtime_State_Tag。 */
template <std::same_as<Task_Runtime_State_Tag> Tag>
void clear_runtime_state_callback();
+24 -28
View File
@@ -21,32 +21,32 @@ struct Renderable::Private : Prev_Private {
using Color_Cache_Visitor = void (*)(void*, const Color_Cache&);
using Color_Cache_Visit = void (*)(Root*, void*, Color_Cache_Visitor);
struct Stage_Dispatch {
Run_Predicate predicate; /* 判断该阶段本次是否执行。 */
Rebuild_Predicate rebuild_predicate; /* 判断已有阶段子图是否重建。 */
Stage_Run run; /* 数据模式执行入口;子图模式为空。 */
Graph_Builder builder; /* 子图模式构建入口;数据模式为空。 */
Run_Predicate predicate; /* 判断该阶段本次是否执行。 */
Rebuild_Predicate rebuild_predicate; /* 判断已有阶段子图是否重建。 */
Stage_Run run; /* 数据模式执行入口;子图模式为空。 */
Graph_Builder builder; /* 子图模式构建入口;数据模式为空。 */
};
struct State_Dispatch {
State_Get current; /* 读取已经发布的 Renderable 状态。 */
State_Get pending; /* 写入本阶段诊断,由完成节点统一交换发布。 */
State_Notify publish; /* 阶段完成后交换 State 双缓冲并发布稳定 current。 */
State_Get current; /* 读取已经发布的 Renderable 状态。 */
State_Get pending; /* 写入本阶段诊断,由完成节点统一交换发布。 */
State_Notify publish; /* 阶段完成后交换 State 双缓冲并发布稳定 current。 */
};
struct Dispatch {
Stage_Dispatch prepare; /* Prepare 阶段分派。 */
Stage_Dispatch paint; /* Paint 阶段分派。 */
State_Dispatch state; /* Renderable 状态访问与发布分派。 */
std::string_view business_name; /* 最终 Renderable 类型的诊断业务名。 */
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<Task_Graph> prepare_graph; /* Prepare 子图模式的当前构建产物。 */
std::unique_ptr<Task_Graph> paint_graph; /* Paint 子图模式的当前构建产物。 */
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<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 子图是否至少成功构建过一次。 */
Task_Graph paint_extension{"renderable.paint.extension"}; /* 外部直接续写的 Paint 完成图。 */
bool prepare_graph_built{}; /* Prepare 子图是否至少成功构建过一次。 */
bool paint_graph_built{}; /* Paint 子图是否至少成功构建过一次。 */
/* CRTP 可覆盖:决定已选中子图模式的 Prepare 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */
bool should_rebuild_prepare_graph(Attached auto* object, const Prop& prop);
/* CRTP 可覆盖:决定已选中子图模式的 Paint 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */
@@ -103,10 +103,8 @@ void Renderable::Private::bind_private_crtp(Object* object) {
auto* value = static_cast<Object*>(root);
auto& private_data = static_cast<typename Object::Private&>(*value->d);
const auto& render_state = static_cast<const State&>(*private_data.state.current);
if (render_state.paint_executed)
visitor(context, value->template pending_buffer<Color_Cache>());
else
visitor(context, value->template current_buffer<Color_Cache>());
if (render_state.paint_executed) visitor(context, value->template pending_buffer<Color_Cache>());
else visitor(context, value->template current_buffer<Color_Cache>());
};
}
}
@@ -117,10 +115,8 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* 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);
for (const std::string_view prefix : {"struct ", "struct "}) 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{