仍有bug

This commit is contained in:
2026-08-24 16:27:27 +08:00
parent ca6d8f8e79
commit f463a756fc
27 changed files with 1201 additions and 898 deletions
+20
View File
@@ -411,6 +411,26 @@ public:
std::lock_guard<Lock> guard(lock);
advance_unlocked();
}
template <detail::State_Tag_In<States> Tag>
void publish_state() {
std::lock_guard<Lock> guard(lock);
data().state.advance();
data().state_callbacks.template notify<Tag>(*data().state.current);
}
template <detail::State_Tag_In<States> Tag, auto... Members, typename Callback>
requires (sizeof...(Members) > 0) &&
(detail::State_Member<Members, State> && ...) &&
std::invocable<Callback, State_Access<State>>
void publish_state(Callback&& callback) {
std::lock_guard<Lock> guard(lock);
(before_state_set(Members), ...);
std::invoke(std::forward<Callback>(callback),
State_Access{*data().state.pending});
(after_state_set(Members), ...);
(emit_state_dependencies<Members>(), ...);
data().state.advance();
data().state_callbacks.template notify<Tag>(*data().state.current);
}
template <std::invocable<const State&> Callback>
void advance(Callback&& callback) {
std::lock_guard<Lock> guard(lock);
+150 -10
View File
@@ -1,9 +1,11 @@
#include "frame.hpp"
#include "frame_statistics.hpp"
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <limits>
#include <optional>
namespace aethera {
namespace {
constexpr std::size_t marker_count = static_cast<std::size_t>(Frame_Trace_Marker::count);
@@ -49,16 +51,154 @@ void Render_Frame::record(Frame_Trace_Measurement measurement, std::uint64_t val
std::uint64_t expected{};
d->measurements[index].compare_exchange_strong(expected, encode_present_value(value_ns), std::memory_order_release, std::memory_order_relaxed);
}
std::vector<Frame_Trace_Point> Render_Frame::trace_points() const {
std::vector<Frame_Trace_Point> result;
result.reserve(marker_count);
for (std::size_t index = 0; index < marker_count; ++index) if (const auto encoded = d->markers[index].load(std::memory_order_acquire)) result.push_back({static_cast<Frame_Trace_Marker>(index), decode_present_value(encoded)});
return result;
}
std::vector<Frame_Trace_Value> Render_Frame::trace_values() const {
std::vector<Frame_Trace_Value> result;
result.reserve(measurement_count);
for (std::size_t index = 0; index < measurement_count; ++index) if (const auto encoded = d->measurements[index].load(std::memory_order_acquire)) result.push_back({static_cast<Frame_Trace_Measurement>(index), decode_present_value(encoded)});
Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) const {
std::array<std::optional<double>, marker_count> markers{};
for (std::size_t index = 0; index < marker_count; ++index) {
const auto encoded = d->markers[index].load(std::memory_order_acquire);
if (encoded)
markers[index] = static_cast<double>(decode_present_value(encoded)) /
1'000'000.0;
}
std::array<double, measurement_count> measurements{};
for (std::size_t index = 0; index < measurement_count; ++index) {
const auto encoded = d->measurements[index].load(std::memory_order_acquire);
if (encoded)
measurements[index] = static_cast<double>(decode_present_value(encoded)) /
1'000'000.0;
}
const auto marker = [&](Frame_Trace_Marker value) {
return markers[static_cast<std::size_t>(value)].value_or(0.0);
};
const auto interval = [&](Frame_Trace_Marker first, Frame_Trace_Marker last) {
const auto start = markers[static_cast<std::size_t>(first)];
const auto finish = markers[static_cast<std::size_t>(last)];
return start && finish ? std::max(0.0, *finish - *start) : 0.0;
};
const auto measurement = [&](Frame_Trace_Measurement value) {
return measurements[static_cast<std::size_t>(value)];
};
Frame_Statistics_Sample result{};
result.set(Frame_Statistic::server_completion_ms,
marker(Frame_Trace_Marker::frame_ready));
result.set(Frame_Statistic::scene_render_ms, interval(
Frame_Trace_Marker::scene_render_started,
Frame_Trace_Marker::scene_render_finished));
result.set(Frame_Statistic::event_dispatch_ms, interval(
Frame_Trace_Marker::event_dispatch_started,
Frame_Trace_Marker::event_dispatch_finished));
result.set(Frame_Statistic::prepare_ms, interval(
Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished));
result.set(Frame_Statistic::paint_ms, interval(
Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished));
result.set(Frame_Statistic::backend_queue_ms, interval(
Frame_Trace_Marker::backend_queue_entered, Frame_Trace_Marker::backend_queue_left));
result.set(Frame_Statistic::gpu_submission_ms, interval(
Frame_Trace_Marker::gpu_submitted, Frame_Trace_Marker::gpu_completed));
result.set(Frame_Statistic::readback_stage_ms, interval(
Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished));
result.set(Frame_Statistic::callback_ms, interval(
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished));
constexpr std::array measurement_statistics{
Frame_Statistic::backend_apply_ms,
Frame_Statistic::backend_plan_ms,
Frame_Statistic::backend_execute_ms,
Frame_Statistic::backend_submit_ms,
Frame_Statistic::gpu_fence_wait_ms,
Frame_Statistic::gpu_render_ms,
Frame_Statistic::gpu_transition_ms,
Frame_Statistic::gpu_copy_ms,
Frame_Statistic::gpu_total_ms,
Frame_Statistic::readback_ms};
for (std::size_t index = 0; index < measurement_statistics.size(); ++index)
result.set(measurement_statistics[index], measurements[index]);
double remaining = marker(Frame_Trace_Marker::frame_ready);
const auto take = [&](double requested) {
const auto value = std::min(remaining, std::max(0.0, requested));
remaining -= value;
return value;
};
const double scene_time = interval(Frame_Trace_Marker::scene_render_started,
Frame_Trace_Marker::scene_render_finished);
const double event_time = std::min(scene_time, interval(
Frame_Trace_Marker::event_dispatch_started,
Frame_Trace_Marker::event_dispatch_finished));
const double prepare_time = std::min(std::max(0.0, scene_time - event_time), interval(
Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished));
const double paint_time = std::min(
std::max(0.0, scene_time - event_time - prepare_time), interval(
Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished));
if (dimension == Frame_Dimension::two_dimensional) {
result.set(Frame_Statistic::pipeline_2d_event_ms, take(event_time));
result.set(Frame_Statistic::pipeline_2d_prepare_ms, take(prepare_time));
result.set(Frame_Statistic::pipeline_2d_paint_ms, take(paint_time));
result.set(Frame_Statistic::pipeline_2d_scene_coordination_ms,
take(std::max(0.0, scene_time - event_time - prepare_time - paint_time)));
result.set(Frame_Statistic::pipeline_2d_callback_ms, take(interval(
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished)));
result.set(Frame_Statistic::pipeline_2d_frame_handoff_ms, remaining);
return result;
}
result.set(Frame_Statistic::pipeline_3d_event_ms, take(event_time));
result.set(Frame_Statistic::pipeline_3d_prepare_ms, take(prepare_time));
result.set(Frame_Statistic::pipeline_3d_submit_graph_ms, take(paint_time));
result.set(Frame_Statistic::pipeline_3d_scene_coordination_ms,
take(std::max(0.0, scene_time - event_time - prepare_time - paint_time)));
const double scene_finished = marker(Frame_Trace_Marker::scene_render_finished);
const double queue_entered = marker(Frame_Trace_Marker::backend_queue_entered);
const double backend_prepare_started = marker(Frame_Trace_Marker::backend_prepare_started);
const double backend_prepare_finished = marker(Frame_Trace_Marker::backend_prepare_finished);
const double submit_queued = marker(Frame_Trace_Marker::backend_submit_queued);
const double queue_left = marker(Frame_Trace_Marker::backend_queue_left);
result.set(Frame_Statistic::pipeline_3d_prepare_queue_ms, take(std::max(
0.0, backend_prepare_started - std::max(scene_finished, queue_entered))));
double preparation_window = std::max(0.0,
backend_prepare_finished - backend_prepare_started);
const auto take_preparation = [&](Frame_Trace_Measurement key) {
const double value = std::min(preparation_window,
std::max(0.0, measurement(key)));
preparation_window -= value;
return take(value);
};
result.set(Frame_Statistic::pipeline_3d_backend_apply_ms,
take_preparation(Frame_Trace_Measurement::backend_apply_ns));
result.set(Frame_Statistic::pipeline_3d_backend_plan_ms,
take_preparation(Frame_Trace_Measurement::backend_plan_ns));
result.set(Frame_Statistic::pipeline_3d_backend_execute_ms,
take_preparation(Frame_Trace_Measurement::backend_execute_ns));
result.set(Frame_Statistic::pipeline_3d_backend_commands_ms, take(preparation_window));
result.set(Frame_Statistic::pipeline_3d_backend_queue_ms,
take(std::max(0.0, queue_left - submit_queued)));
const double gpu_submitted = marker(Frame_Trace_Marker::gpu_submitted);
double submit_window = std::max(0.0, gpu_submitted - queue_left);
const double measured_submit = std::min(submit_window, std::max(
0.0, measurement(Frame_Trace_Measurement::backend_submit_ns)));
result.set(Frame_Statistic::pipeline_3d_backend_submit_ms, take(measured_submit));
submit_window -= measured_submit;
result.set(Frame_Statistic::pipeline_3d_submit_handoff_ms, take(submit_window));
double gpu_window = interval(Frame_Trace_Marker::gpu_submitted,
Frame_Trace_Marker::gpu_completed);
const auto take_gpu = [&](Frame_Trace_Measurement key) {
const double value = std::min(gpu_window, std::max(0.0, measurement(key)));
gpu_window -= value;
return take(value);
};
result.set(Frame_Statistic::pipeline_3d_gpu_render_ms,
take_gpu(Frame_Trace_Measurement::gpu_render_ns));
result.set(Frame_Statistic::pipeline_3d_gpu_transition_ms,
take_gpu(Frame_Trace_Measurement::gpu_transition_ns));
result.set(Frame_Statistic::pipeline_3d_gpu_copy_ms,
take_gpu(Frame_Trace_Measurement::gpu_copy_ns));
result.set(Frame_Statistic::pipeline_3d_gpu_sync_ms, take(gpu_window));
result.set(Frame_Statistic::pipeline_3d_readback_ms, take(interval(
Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished)));
result.set(Frame_Statistic::pipeline_3d_callback_ms, take(interval(
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished)));
result.set(Frame_Statistic::pipeline_3d_completion_handoff_ms, remaining);
return result;
}
}
+4 -2
View File
@@ -4,6 +4,8 @@
#include <memory>
#include <vector>
namespace aethera {
enum class Frame_Dimension : std::uint8_t;
struct Frame_Statistics_Sample;
enum class Frame_Trace_Marker : std::uint8_t {
created,
scene_render_requested,
@@ -43,6 +45,7 @@ enum class Frame_Trace_Measurement : std::uint8_t {
count
};
struct Frame_Identity {
friend bool operator==(const Frame_Identity&, const Frame_Identity&) = default;
std::uint64_t sequence{}; /* 外部帧管理器分配的单调帧序号。 */
std::uint64_t correlation_id{}; /* 与调用方请求关联的标识;零值表示未关联。 */
};
@@ -62,8 +65,7 @@ public:
[[nodiscard]] std::uint64_t created_time_unix_ns() const noexcept;
void mark(Frame_Trace_Marker marker) noexcept;
void record(Frame_Trace_Measurement measurement, std::uint64_t value_ns) noexcept;
[[nodiscard]] std::vector<Frame_Trace_Point> trace_points() const;
[[nodiscard]] std::vector<Frame_Trace_Value> trace_values() const;
[[nodiscard]] Frame_Statistics_Sample statistics(Frame_Dimension dimension) const;
protected:
/* 物理帧槽再次承载新逻辑帧时,重建其唯一身份和诊断时间原点。 */
void begin(Frame_Identity identity) noexcept;
+227
View File
@@ -0,0 +1,227 @@
#include "frame_statistics.hpp"
#include <algorithm>
#include <cmath>
#include <stdexcept>
namespace aethera {
void Frame_Statistics_Sample::set(Frame_Statistic statistic, double value) noexcept {
const auto index = static_cast<std::size_t>(statistic);
if (index >= values.size() || !std::isfinite(value)) return;
values[index] = value;
present.set(index);
}
Sliding_Statistics::Sliding_Statistics() : Sliding_Statistics(600) {}
Sliding_Statistics::Sliding_Statistics(std::size_t capacity)
: values_(capacity), trimmed_values_(capacity),
minimum_(capacity, true), maximum_(capacity, false) {
if (capacity == 0)
throw std::invalid_argument("statistics capacity must be positive");
}
Sliding_Statistics::Extremum_Queue::Extremum_Queue(
std::size_t capacity, bool minimum)
: nodes_(capacity + 1U), minimum_(minimum) {}
void Sliding_Statistics::Extremum_Queue::submit(
double sample, std::uint64_t sequence, std::size_t window) noexcept {
const auto count = nodes_.size();
while (head_ != tail_ &&
nodes_[head_].sequence + window <= sequence)
head_ = (head_ + 1U) % count;
while (head_ != tail_) {
const auto previous = (tail_ + count - 1U) % count;
const bool superseded = minimum_ ? nodes_[previous].value >= sample
: nodes_[previous].value <= sample;
if (!superseded) break;
tail_ = previous;
}
nodes_[tail_] = Node{sample, sequence};
tail_ = (tail_ + 1U) % count;
}
double Sliding_Statistics::Extremum_Queue::value() const noexcept {
return head_ == tail_ ? 0.0 : nodes_[head_].value;
}
void Sliding_Statistics::Extremum_Queue::reset() noexcept {
head_ = 0;
tail_ = 0;
}
Sliding_Statistics::Quantile_Estimator::Quantile_Estimator(
double probability) noexcept : probability_(probability) {}
void Sliding_Statistics::Quantile_Estimator::submit(double sample) noexcept {
if (count_ < initial_.size()) {
initial_[count_++] = sample;
if (count_ != initial_.size()) return;
std::ranges::sort(initial_);
heights_ = initial_;
positions_ = {1.0, 2.0, 3.0, 4.0, 5.0};
desired_ = {1.0, 1.0 + 2.0 * probability_,
1.0 + 4.0 * probability_,
3.0 + 2.0 * probability_, 5.0};
increments_ = {0.0, probability_ / 2.0, probability_,
(1.0 + probability_) / 2.0, 1.0};
return;
}
++count_;
std::size_t bucket{};
if (sample < heights_[0]) {
heights_[0] = sample;
} else if (sample >= heights_[4]) {
heights_[4] = sample;
bucket = 3;
} else {
while (bucket < 3 && sample >= heights_[bucket + 1]) ++bucket;
}
for (std::size_t index = bucket + 1; index < positions_.size(); ++index)
positions_[index] += 1.0;
for (std::size_t index = 0; index < desired_.size(); ++index)
desired_[index] += increments_[index];
for (std::size_t index = 1; index < 4; ++index) {
const double distance = desired_[index] - positions_[index];
const double direction = distance >= 1.0 ? 1.0 : distance <= -1.0 ? -1.0 : 0.0;
if (direction == 0.0 ||
(direction > 0.0 && positions_[index + 1] - positions_[index] <= 1.0) ||
(direction < 0.0 && positions_[index - 1] - positions_[index] >= -1.0))
continue;
const double left = positions_[index] - positions_[index - 1];
const double right = positions_[index + 1] - positions_[index];
const double estimate = heights_[index] + direction / (left + right) *
((left + direction) * (heights_[index + 1] - heights_[index]) / right +
(right - direction) * (heights_[index] - heights_[index - 1]) / left);
if (heights_[index - 1] < estimate && estimate < heights_[index + 1])
heights_[index] = estimate;
else {
const auto neighbor = static_cast<std::size_t>(
static_cast<std::ptrdiff_t>(index) +
static_cast<std::ptrdiff_t>(direction));
heights_[index] += direction *
(heights_[neighbor] - heights_[index]) /
(positions_[neighbor] - positions_[index]);
}
positions_[index] += direction;
}
}
double Sliding_Statistics::Quantile_Estimator::value() const noexcept {
if (count_ == 0) return 0.0;
if (count_ >= initial_.size()) return heights_[2];
auto ordered = initial_;
std::sort(ordered.begin(), ordered.begin() + static_cast<std::ptrdiff_t>(count_));
const auto index = std::min(count_ - 1U, static_cast<std::size_t>(
std::ceil(probability_ * static_cast<double>(count_))) - 1U);
return ordered[index];
}
void Sliding_Statistics::Quantile_Estimator::reset() noexcept {
initial_ = {};
heights_ = {};
positions_ = {};
desired_ = {};
increments_ = {};
count_ = 0;
}
Statistic_State Sliding_Statistics::submit(double value) {
if (!std::isfinite(value)) return state_;
if (size_ == values_.size()) {
if (!trimmed_window_initialized_) {
const double lower = p05_.value();
const double upper = p95_.value();
trimmed_sum_ = 0.0;
for (std::size_t index = 0; index < size_; ++index) {
trimmed_values_[index] = std::clamp(values_[index], lower, upper);
trimmed_sum_ += trimmed_values_[index];
}
trimmed_window_initialized_ = true;
}
const double discarded = values_[next_];
sum_ -= discarded;
squared_sum_ -= discarded * discarded;
trimmed_sum_ -= trimmed_values_[next_];
}
p05_.submit(value);
p50_.submit(value);
p95_.submit(value);
p99_.submit(value);
minimum_.submit(value, sequence_, values_.size());
maximum_.submit(value, sequence_, values_.size());
++sequence_;
const double trimmed = trimmed_window_initialized_
? std::clamp(value, p05_.value(), p95_.value()) : value;
values_[next_] = value;
trimmed_values_[next_] = trimmed;
sum_ += value;
squared_sum_ += value * value;
trimmed_sum_ += trimmed;
next_ = (next_ + 1U) % values_.size();
size_ = std::min(size_ + 1U, values_.size());
const double mean = sum_ / static_cast<double>(size_);
state_ = Statistic_State{
size_, value, minimum_.value(), maximum_.value(), mean,
trimmed_sum_ / static_cast<double>(size_),
std::sqrt(std::max(0.0,
squared_sum_ / static_cast<double>(size_) - mean * mean)),
p50_.value(), p95_.value(), p99_.value()};
return state_;
}
const Statistic_State& Sliding_Statistics::state() const noexcept { return state_; }
void Sliding_Statistics::reset() noexcept {
size_ = 0;
next_ = 0;
sum_ = 0.0;
squared_sum_ = 0.0;
trimmed_sum_ = 0.0;
trimmed_window_initialized_ = false;
sequence_ = 0;
minimum_.reset();
maximum_.reset();
p05_.reset();
p50_.reset();
p95_.reset();
p99_.reset();
state_ = {};
}
Frame_Statistics_Accumulator::Frame_Statistics_Accumulator(std::size_t capacity) {
values_.reserve(frame_statistic_count);
for (std::size_t index = 0; index < frame_statistic_count; ++index)
values_.emplace_back(capacity);
}
const Frame_Statistics_State& Frame_Statistics_Accumulator::submit(
const Render_Frame& frame, Frame_Dimension dimension) {
auto sample = frame.statistics(dimension);
const auto now = std::chrono::steady_clock::now();
const auto frame_identity = frame.identity();
if (previous_completion_ != std::chrono::steady_clock::time_point{}) {
sample.set(Frame_Statistic::frame_interval_ms,
std::chrono::duration<double, std::milli>(
now - previous_completion_).count());
if (frame_identity.sequence > previous_sequence_ + 1U)
state_.dropped_sequences += frame_identity.sequence - previous_sequence_ - 1U;
}
previous_completion_ = now;
previous_sequence_ = frame_identity.sequence;
state_.identity = frame_identity;
state_.created_time_unix_ns = frame.created_time_unix_ns();
for (std::size_t index = 0; index < sample.values.size(); ++index) {
if (!sample.present.test(index)) continue;
state_.values[index] = values_[index].submit(sample.values[index]);
}
return state_;
}
void Frame_Statistics_Accumulator::reset() noexcept {
for (auto& value : values_) value.reset();
state_ = {};
previous_completion_ = {};
previous_sequence_ = 0;
}
}
+168
View File
@@ -0,0 +1,168 @@
#pragma once
#include "frame.hpp"
#include <array>
#include <bitset>
#include <chrono>
#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 {
server_completion_ms,
scene_render_ms,
event_dispatch_ms,
prepare_ms,
paint_ms,
backend_queue_ms,
gpu_submission_ms,
readback_stage_ms,
callback_ms,
backend_apply_ms,
backend_plan_ms,
backend_execute_ms,
backend_submit_ms,
gpu_fence_wait_ms,
gpu_render_ms,
gpu_transition_ms,
gpu_copy_ms,
gpu_total_ms,
readback_ms,
pipeline_2d_event_ms,
pipeline_2d_prepare_ms,
pipeline_2d_paint_ms,
pipeline_2d_scene_coordination_ms,
pipeline_2d_callback_ms,
pipeline_2d_frame_handoff_ms,
pipeline_3d_event_ms,
pipeline_3d_prepare_ms,
pipeline_3d_submit_graph_ms,
pipeline_3d_scene_coordination_ms,
pipeline_3d_prepare_queue_ms,
pipeline_3d_backend_apply_ms,
pipeline_3d_backend_plan_ms,
pipeline_3d_backend_execute_ms,
pipeline_3d_backend_commands_ms,
pipeline_3d_backend_queue_ms,
pipeline_3d_backend_submit_ms,
pipeline_3d_submit_handoff_ms,
pipeline_3d_gpu_render_ms,
pipeline_3d_gpu_transition_ms,
pipeline_3d_gpu_copy_ms,
pipeline_3d_gpu_sync_ms,
pipeline_3d_readback_ms,
pipeline_3d_callback_ms,
pipeline_3d_completion_handoff_ms,
frame_interval_ms,
count
};
inline constexpr 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{};
double minimum{};
double maximum{};
double average{};
double trimmed_average{};
double variability{};
double p50{};
double p95{};
double p99{};
bool operator==(const Statistic_State&) const = default;
};
class 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 {
public:
explicit Quantile_Estimator(double probability) noexcept;
void submit(double value) noexcept;
[[nodiscard]] double value() const noexcept;
void reset() noexcept;
private:
double probability_{};
std::array<double, 5> initial_{};
std::array<double, 5> heights_{};
std::array<double, 5> positions_{};
std::array<double, 5> desired_{};
std::array<double, 5> increments_{};
std::size_t count_{};
};
/* 预分配单调队列,O(1) 摊还维护精确滑动窗口极值。 */
class Extremum_Queue final {
public:
Extremum_Queue(std::size_t capacity, bool minimum);
void submit(double value, std::uint64_t sequence,
std::size_t window) noexcept;
[[nodiscard]] double value() const noexcept;
void reset() noexcept;
private:
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_;
std::size_t size_{};
std::size_t next_{};
double sum_{};
double squared_sum_{};
double trimmed_sum_{};
bool trimmed_window_initialized_{};
std::uint64_t sequence_{};
Extremum_Queue minimum_;
Extremum_Queue maximum_;
Quantile_Estimator p05_{0.05};
Quantile_Estimator p50_{0.50};
Quantile_Estimator p95_{0.95};
Quantile_Estimator p99_{0.99};
Statistic_State state_{};
};
struct Frame_Statistics_State {
/* 可直接通过 Scene State 双缓冲发布的定长结果;不含统计器内部样本。 */
std::array<Statistic_State, frame_statistic_count> values{};
Frame_Identity identity{};
std::uint64_t created_time_unix_ns{};
std::uint64_t dropped_sequences{};
bool operator==(const Frame_Statistics_State&) const = default;
};
class Frame_Statistics_Accumulator final {
public:
explicit Frame_Statistics_Accumulator(std::size_t capacity = 600);
[[nodiscard]] const Frame_Statistics_State& submit(
const Render_Frame& frame, Frame_Dimension dimension);
void reset() noexcept;
private:
std::vector<Sliding_Statistics> values_;
Frame_Statistics_State state_{};
std::chrono::steady_clock::time_point previous_completion_{};
std::uint64_t previous_sequence_{};
};
}
+3 -2
View File
@@ -202,14 +202,13 @@ private:
std::atomic_bool state_callback_enabled{};
void create_executor(std::size_t workers, std::shared_ptr<tf::WorkerInterface> worker_interface) {
executor = std::make_unique<tf::Executor>(workers, std::move(worker_interface));
observer = executor->make_observer<Task_Observer>();
observer.reset();
state = {};
}
void ensure_executor() {
std::lock_guard guard(state_mutex);
if (executor) return;
executor = std::make_unique<tf::Executor>();
observer = executor->make_observer<Task_Observer>();
}
void publish_state() {
if (!state_callback_enabled.load(std::memory_order_acquire)) return;
@@ -283,7 +282,9 @@ public:
return memory;
}
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);
}