From 0d8955cdf21e04853490998bad261b545c8da6ef Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 4 Sep 2026 22:35:59 +0800 Subject: [PATCH] =?UTF-8?q?tracy=E5=BC=95=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernel/include/observation/Serialize.hpp | 38 ++++++++ kernel/kernel/include/observation/Tracy.hpp | 91 +++++++++++++++++++ .../test/model/Observation_Serialize.cpp | 45 +++++++++ kernel/main.cmake | 3 +- mcp/main.cmake | 2 - 5 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 kernel/kernel/include/observation/Serialize.hpp create mode 100644 kernel/kernel/include/observation/Tracy.hpp create mode 100644 kernel/kernel/test/model/Observation_Serialize.cpp diff --git a/kernel/kernel/include/observation/Serialize.hpp b/kernel/kernel/include/observation/Serialize.hpp new file mode 100644 index 0000000..1942387 --- /dev/null +++ b/kernel/kernel/include/observation/Serialize.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include +#include +#include +#include +namespace aethera::observation { +namespace detail { +void trace_serialize() = delete; +template +concept Custom_Serializable = requires(Sink& sink, const T& value) { trace_serialize(sink, value); }; +template +void serialize_custom(Sink& sink, const T& value) { + trace_serialize(sink, value); +} +template +void serialize_field(Sink& sink, std::string_view name, const T& value) { + using Value = std::remove_cvref_t; + if constexpr (Custom_Serializable) serialize_custom(sink, value); + else sink.field(name, value); +} +template +void serialize_aggregate(Sink& sink, const T& value, std::index_sequence) { + (serialize_field(sink, boost::pfr::get_name(), boost::pfr::get(value)), ...); +} +} +template +void serialize(Sink& sink, const T& value) { + using Value = std::remove_cvref_t; + if constexpr (detail::Custom_Serializable) { + detail::serialize_custom(sink, value); + } else { + static_assert(std::is_aggregate_v, "observation serialization requires an aggregate or trace_serialize overload"); + detail::serialize_aggregate(sink, value, std::make_index_sequence>{}); + } +} +} diff --git a/kernel/kernel/include/observation/Tracy.hpp b/kernel/kernel/include/observation/Tracy.hpp new file mode 100644 index 0000000..dcb034c --- /dev/null +++ b/kernel/kernel/include/observation/Tracy.hpp @@ -0,0 +1,91 @@ +#pragma once +#include "Serialize.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef TRACY_ENABLE +#include +#endif +namespace aethera::observation { +class Tracy_Text_Sink { +public: + template + void field(std::string_view name, const T& value) { + if (size_ != 0) append(" "); + append(name); + append("="); + append_value(value); + } + [[nodiscard]] const char* data() const noexcept { + return buffer_.data(); + } + [[nodiscard]] std::size_t size() const noexcept { + return size_; + } + [[nodiscard]] bool empty() const noexcept { + return size_ == 0; + } +private: + template + static constexpr bool always_false = false; + void append(std::string_view value) { + const auto available = buffer_.size() - size_; + const auto count = value.size() < available ? value.size() : available; + if (count == 0) return; + std::memcpy(buffer_.data() + size_, value.data(), count); + size_ += count; + } + template + void append_number(T value) { + std::array text{}; + const auto [end, error] = std::to_chars(text.data(), text.data() + text.size(), value); + if (error == std::errc{}) append(std::string_view(text.data(), static_cast(end - text.data()))); + } + template + void append_value(const T& value) { + using Value = std::remove_cvref_t; + if constexpr (std::same_as) { + append(value ? "true" : "false"); + } else if constexpr (std::is_enum_v) { + append(magic_enum::enum_name(value)); + } else if constexpr (std::integral || std::floating_point) { + append_number(value); + } else if constexpr (std::same_as) { + append(value); + } else if constexpr (std::same_as) { + append(value); + } else if constexpr (std::same_as) { + append(std::string_view(&value, 1)); + } else if constexpr (std::same_as || std::same_as) { + append(value == nullptr ? std::string_view("null") : std::string_view(value)); + } else if constexpr (requires { std::string_view{value}; }) { + append(std::string_view{value}); + } else { + static_assert(always_false, "Tracy tag field type requires a trace_serialize overload that emits supported scalar fields"); + } + } + std::array buffer_{}; + std::size_t size_{}; +}; +#ifdef TRACY_ENABLE +template +void attach_tracy_tags(tracy::ScopedZone& zone, Make_Tags&& make_tags) { + if (!zone.IsActive()) return; + Tracy_Text_Sink sink; + serialize(sink, make_tags()); + if (!sink.empty()) zone.Text(sink.data(), sink.size()); +} +#endif +} +#ifdef TRACY_ENABLE +#define AETHERA_TRACE_ZONE(name, ...) ZoneScopedN(name); ::aethera::observation::attach_tracy_tags(___tracy_scoped_zone, [&]() -> decltype(auto) { return (__VA_ARGS__); }) +#else +#define AETHERA_TRACE_ZONE(name, ...) +#endif diff --git a/kernel/kernel/test/model/Observation_Serialize.cpp b/kernel/kernel/test/model/Observation_Serialize.cpp new file mode 100644 index 0000000..51d328c --- /dev/null +++ b/kernel/kernel/test/model/Observation_Serialize.cpp @@ -0,0 +1,45 @@ +#include "observation/Tracy.hpp" +#include +#include +#include +namespace { +enum class Test_Phase { + prepare, + publish +}; +namespace custom { +struct Resource_Id { + std::uint64_t value; +}; +template +void trace_serialize(Sink& sink, const Resource_Id& value) { + sink.field("resource", value.value); +} +} +struct Test_Tags { + std::uint64_t frame; + custom::Resource_Id resource; + Test_Phase phase; + bool dirty; +}; +namespace custom { +struct Test_Tags { + std::uint64_t resource; +}; +template +void trace_serialize(Sink& sink, const Test_Tags& value) { + sink.field("id", value.resource); + sink.field("kind", "resource"); +} +} +TEST(Observation_Serialize, Aggregate_Uses_Pfr_And_Field_Customization) { + aethera::observation::Tracy_Text_Sink sink; + aethera::observation::serialize(sink, Test_Tags{17, {23}, Test_Phase::prepare, true}); + EXPECT_EQ(std::string_view(sink.data(), sink.size()), "frame=17 resource=23 phase=prepare dirty=true"); +} +TEST(Observation_Serialize, Adl_Serializer_Overrides_Aggregate_Fallback) { + aethera::observation::Tracy_Text_Sink sink; + aethera::observation::serialize(sink, custom::Test_Tags{41}); + EXPECT_EQ(std::string_view(sink.data(), sink.size()), "id=41 kind=resource"); +} +} diff --git a/kernel/main.cmake b/kernel/main.cmake index cebdc2e..dd80b04 100644 --- a/kernel/main.cmake +++ b/kernel/main.cmake @@ -1,7 +1,7 @@ rcl_init(aethera_kernel) include(${CMAKE_CURRENT_LIST_DIR}/cmake/taskflow.cmake) include(${CMAKE_CURRENT_LIST_DIR}/cmake/tracy.cmake) -set(Aethera_Kernel_dependencies global::proxy aethera_kernel::tracy aethera_kernel::taskflow global::magic_enum) +set(Aethera_Kernel_dependencies global::proxy aethera_kernel::taskflow global::magic_enum global::pfr) set(Aethera_BUILD_TESTS TRUE) if (Aethera_BUILD_TESTS) enable_testing() @@ -37,6 +37,7 @@ append_glob_source(Aethera_Kernel_Core_sources "${Aethera_Kernel_source_dir}") add_library(Aethera_Kernel_Core STATIC ${Aethera_Kernel_Core_sources}) target_include_directories(Aethera_Kernel_Core PUBLIC "$" + "$" "$" "$" ) diff --git a/mcp/main.cmake b/mcp/main.cmake index 88c8a5a..a03e0fb 100644 --- a/mcp/main.cmake +++ b/mcp/main.cmake @@ -1,7 +1,6 @@ include("${CMAKE_CURRENT_LIST_DIR}/../web_server/cmake/rely.cmake") set(Aethera_MCP_dependencies global::drogon - global::pfr ) rcl_add_dependency_action_targets(Aethera_MCP_env ${Aethera_MCP_dependencies}) set_target_properties(Aethera_MCP_env PROPERTIES FOLDER Aethera_MCP) @@ -42,7 +41,6 @@ append_glob_source(Aethera_MCP_core_sources "${Aethera_MCP_core_dir}") add_library(Aethera_MCP_Core STATIC ${Aethera_MCP_core_sources}) target_include_directories(Aethera_MCP_Core PUBLIC "$" - "$" "$") target_compile_features(Aethera_MCP_Core PUBLIC cxx_std_23) target_compile_definitions(Aethera_MCP_Core PUBLIC NOMINMAX)