This commit is contained in:
2026-08-27 17:54:05 +08:00
parent bc0bb24aea
commit 14f5421925
16 changed files with 234 additions and 205 deletions
+12 -54
View File
@@ -1,81 +1,39 @@
#include "Frame_Pacing_Policy.hpp"
#include <cmath>
#include <stdexcept>
#include <utility>
namespace aethera {
Frame_Pacing_Policy::Frame_Pacing_Policy()
: properties_(std::make_shared<const Frame_Pacing_Properties>()) {}
Frame_Pacing_Policy::Frame_Pacing_Policy() = default;
Frame_Pacing_Properties Frame_Pacing_Policy::read() const {
return *properties_.load(std::memory_order_acquire);
}
template <typename Edit>
void Frame_Pacing_Policy::update(Edit&& edit) {
auto current = properties_.load(std::memory_order_acquire);
for (;;) {
auto next = std::make_shared<Frame_Pacing_Properties>(*current);
std::forward<Edit>(edit)(*next);
std::shared_ptr<const Frame_Pacing_Properties> desired = next;
if (properties_.compare_exchange_weak(
current, desired, std::memory_order_release,
std::memory_order_acquire))
return;
}
return {
render_enabled_.load(std::memory_order_acquire),
video_enabled_.load(std::memory_order_acquire),
mode_.load(std::memory_order_acquire),
fixed_rate_fps_.load(std::memory_order_acquire)};
}
void Frame_Pacing_Policy::set_render_enabled(bool enabled) {
update([enabled](auto& value) { value.render_enabled = enabled; });
render_enabled_.store(enabled, std::memory_order_release);
}
void Frame_Pacing_Policy::set_video_enabled(bool enabled) {
update([enabled](auto& value) { value.video_enabled = enabled; });
video_enabled_.store(enabled, std::memory_order_release);
}
void Frame_Pacing_Policy::set_mode(Frame_Pacing_Mode mode) {
update([mode](auto& value) { value.mode = mode; });
mode_.store(mode, std::memory_order_release);
}
void Frame_Pacing_Policy::set_fixed_rate(double fps) {
if (!std::isfinite(fps) || fps <= 0.0)
throw std::invalid_argument("frame pacing fps must be positive");
update([fps](auto& value) { value.fixed_rate_fps = fps; });
fixed_rate_fps_.store(fps, std::memory_order_release);
}
double Frame_Pacing_Policy::scheduled_rate_fps() const {
const auto value = read();
if (!value.render_enabled || value.mode == Frame_Pacing_Mode::manual)
return 0.0;
return value.mode == Frame_Pacing_Mode::maximum_rate
? value.maximum_rate_fps : value.fixed_rate_fps;
bool Frame_Pacing_Policy::request_immediate() const noexcept {
return render_enabled_.load(std::memory_order_acquire);
}
bool Frame_Pacing_Policy::accept_periodic_tick(double time_milliseconds) {
static_cast<void>(time_milliseconds);
const auto value = read();
if (!value.render_enabled || value.mode == Frame_Pacing_Mode::manual)
return false;
if (skip_next_periodic_.exchange(false, std::memory_order_acq_rel))
return false;
return true;
}
bool Frame_Pacing_Policy::request_immediate() {
const auto value = read();
if (!value.render_enabled) return false;
/* immediate 与下一次周期机会合并,真正的 busy/latest 语义由 Plot 管理。 */
skip_next_periodic_.store(true, std::memory_order_release);
return true;
}
void Frame_Pacing_Policy::frame_submitted() {}
bool Frame_Pacing_Policy::frame_completed() {
return false;
}
void Frame_Pacing_Policy::frame_rejected() {}
}
+9 -22
View File
@@ -1,7 +1,6 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <memory>
namespace aethera {
@@ -12,11 +11,10 @@ enum struct Frame_Pacing_Mode : std::uint8_t {
};
struct Frame_Pacing_Properties {
bool render_enabled{true};
bool video_enabled{true};
Frame_Pacing_Mode mode{Frame_Pacing_Mode::fixed_rate};
double fixed_rate_fps{30.0};
double maximum_rate_fps{100.0};
bool render_enabled{true}; /* 查询时 Plot 是否接受任何帧请求。 */
bool video_enabled{true}; /* 查询时完成帧是否需要生成视频像素。 */
Frame_Pacing_Mode mode{Frame_Pacing_Mode::fixed_rate}; /* 查询时服务端帧驱动模式。 */
double fixed_rate_fps{30.0}; /* fixed_rate 模式目标帧率。 */
};
/*
@@ -32,24 +30,13 @@ struct Frame_Pacing_Policy final {
void set_mode(Frame_Pacing_Mode mode);
void set_fixed_rate(double fps);
/* 当前策略应向全局 Frame_Scheduler 注册的频率;0 表示事件驱动/关闭周期。 */
[[nodiscard]] double scheduled_rate_fps() const;
/*
* 周期 tick 只判断策略/周期语义,不再承担 Scene in-flight 门控。
* 实际渲染准入由调用方在 Scene::advance 前控制;忙时保留 latest pending tick。
*/
[[nodiscard]] bool accept_periodic_tick(double time_milliseconds);
[[nodiscard]] bool request_immediate();
void frame_submitted();
[[nodiscard]] bool frame_completed();
void frame_rejected();
[[nodiscard]] bool request_immediate() const noexcept;
private:
template <typename Edit> void update(Edit&& edit);
std::atomic<std::shared_ptr<const Frame_Pacing_Properties>> properties_;
std::atomic_bool skip_next_periodic_{};
std::atomic_bool render_enabled_{true}; /* 当前 Plot 是否接受任何帧请求。 */
std::atomic_bool video_enabled_{true}; /* 完成帧是否需要生成视频像素。 */
std::atomic<Frame_Pacing_Mode> mode_{Frame_Pacing_Mode::fixed_rate}; /* 当前服务端帧驱动模式。 */
std::atomic<double> fixed_rate_fps_{30.0}; /* fixed_rate 模式目标帧率。 */
};
}
-27
View File
@@ -4,7 +4,6 @@
#include <algorithm>
#include <atomic>
#include <cmath>
#include <future>
#include <limits>
#include <stdexcept>
#include <thread>
@@ -63,7 +62,6 @@ struct Frame_Scheduler::Private {
Command_Type type{};
std::shared_ptr<Timer::State> state{};
Nanoseconds value{};
std::shared_ptr<std::promise<void>> completion{};
};
TimerWheel wheel{};
@@ -72,7 +70,6 @@ struct Frame_Scheduler::Private {
moodycamel::BlockingConcurrentQueue<Command> commands{256};
std::atomic_uint64_t next_id{1};
std::atomic_bool stopping{};
std::thread::id control_thread_id{};
std::jthread thread{};
Private() : thread([this](std::stop_token stop) { run(stop); }) {}
@@ -149,10 +146,6 @@ struct Frame_Scheduler::Private {
case Command_Type::stop:
break;
}
if (command.completion) {
try { command.completion->set_value(); }
catch (...) {}
}
}
void process_commands(Scheduler_Clock::time_point now) {
@@ -210,7 +203,6 @@ struct Frame_Scheduler::Private {
}
void run(std::stop_token stop) noexcept {
control_thread_id = std::this_thread::get_id();
try {
while (!stop.stop_requested() &&
!stopping.load(std::memory_order_acquire)) {
@@ -301,25 +293,6 @@ void Frame_Scheduler::Timer::cancel() noexcept {
state_->owner->enqueue({Private::Command_Type::cancel, state_, {}});
}
void Frame_Scheduler::Timer::cancel_and_wait() noexcept {
if (!state_ || !state_->owner) return;
state_->alive.store(false, std::memory_order_release);
auto* owner = state_->owner;
if (owner->control_thread_id == std::this_thread::get_id()) {
state_->event.cancel();
state_->mode = State::Mode::stopped;
return;
}
try {
auto completion = std::make_shared<std::promise<void>>();
auto future = completion->get_future();
owner->enqueue({Private::Command_Type::cancel, state_, {}, completion});
future.wait();
}
catch (...) {}
}
bool Frame_Scheduler::Timer::valid() const noexcept {
return static_cast<bool>(state_);
}
-1
View File
@@ -40,7 +40,6 @@ struct Frame_Scheduler final {
/* 单次延迟;delay <= 0 会在下一个 Scheduler 周期尽快触发。 */
void start_once(std::chrono::nanoseconds delay);
void cancel() noexcept;
void cancel_and_wait() noexcept; /* 销毁拥有 callback 的对象前使用,保证控制线程不再执行该 callback。 */
[[nodiscard]] bool valid() const noexcept;
private:
+5 -1
View File
@@ -24,6 +24,7 @@ struct Render_Frame::Private {
std::vector<Taskflow_Task_Trace> tasks{}; /* 仅对应 worker 写入,捕获结束后统一读取。 */
};
Frame_Identity identity{}; /* 外部帧管理器提供且终生不变的帧身份。 */
Frame_Request_Source request_source{}; /* 当前逻辑帧的策略触发来源。 */
std::chrono::steady_clock::time_point created_at{}; /* 所有 elapsed_ns 使用的单调时钟原点。 */
std::uint64_t created_time_unix_ns{}; /* 用于跨进程展示的创建 Unix 时间,单位为纳秒。 */
std::array<std::atomic_uint64_t, marker_count> markers{}; /* 每种时间点首次出现时的 elapsed_ns 加一编码。 */
@@ -35,8 +36,10 @@ struct Render_Frame::Private {
Render_Frame::Render_Frame(Frame_Identity identity) : d(std::make_unique<Private>()) {
begin(identity);
}
void Render_Frame::begin(Frame_Identity identity) noexcept {
void Render_Frame::begin(Frame_Identity identity,
Frame_Request_Source source) noexcept {
d->identity = identity;
d->request_source = source;
d->created_at = std::chrono::steady_clock::now();
const auto system_now = std::chrono::system_clock::now().time_since_epoch();
d->created_time_unix_ns = static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(system_now).count());
@@ -154,6 +157,7 @@ bool Render_Frame::taskflow_trace_requested() const noexcept {
Taskflow_Frame_Trace Render_Frame::take_taskflow_trace() {
Taskflow_Frame_Trace result{};
result.identity = d->identity;
result.request_source = d->request_source;
result.created_time_unix_ns = d->created_time_unix_ns;
result.worker_count = d->taskflow_workers.size();
for (std::size_t index = 0; index < marker_count; ++index) {
+9 -1
View File
@@ -9,6 +9,12 @@ namespace detail {
struct Taskflow_Frame_Access;
}
struct Frame_Statistics_Sample;
enum struct Frame_Request_Source : std::uint8_t {
unspecified,
periodic,
immediate,
maximum_rate
};
enum struct Frame_Trace_Marker : std::uint8_t {
created,
plot_update_started,
@@ -121,6 +127,7 @@ struct Taskflow_Task_Trace {
};
struct Taskflow_Frame_Trace {
Frame_Identity identity{}; /* 该执行图所属逻辑渲染帧。 */
Frame_Request_Source request_source{}; /* 该逻辑帧由周期、手动或最大吞吐策略触发。 */
std::uint64_t created_time_unix_ns{}; /* 帧创建 Unix 时间,单位纳秒。 */
std::size_t worker_count{}; /* 捕获时全局 Executor 的 worker 数。 */
std::vector<Frame_Trace_Point> markers{}; /* 与该帧 DAG 共用时间原点的原始流水线时间点。 */
@@ -142,7 +149,8 @@ public:
[[nodiscard]] Taskflow_Frame_Trace take_taskflow_trace();
protected:
/* 物理帧槽再次承载新逻辑帧时,重建其唯一身份和诊断时间原点。 */
void begin(Frame_Identity identity) noexcept;
void begin(Frame_Identity identity,
Frame_Request_Source source = Frame_Request_Source::unspecified) noexcept;
private:
struct Private;
friend struct detail::Taskflow_Frame_Access;
+19
View File
@@ -1,4 +1,5 @@
#include "scene.hpp"
#include "Frame_Pacing_Policy.hpp"
#include <gtest/gtest.h>
#include <atomic>
#include <unordered_set>
@@ -50,6 +51,24 @@ using Scene = aethera::Scene;
inline Direct_Renderable::Private& Direct_Renderable::data_for_test() {
return static_cast<Private&>(*d);
}
TEST(frame_pacing_policy, independent_properties_do_not_publish_copied_versions) {
aethera::Frame_Pacing_Policy policy;
policy.set_mode(aethera::Frame_Pacing_Mode::maximum_rate);
policy.set_fixed_rate(47.5);
policy.set_video_enabled(false);
const auto properties = policy.read();
EXPECT_TRUE(properties.render_enabled);
EXPECT_FALSE(properties.video_enabled);
EXPECT_EQ(properties.mode, aethera::Frame_Pacing_Mode::maximum_rate);
EXPECT_DOUBLE_EQ(properties.fixed_rate_fps, 47.5);
EXPECT_TRUE(policy.request_immediate());
policy.set_render_enabled(false);
EXPECT_FALSE(policy.request_immediate());
EXPECT_THROW(policy.set_fixed_rate(0.0), std::invalid_argument);
}
inline Graph_Renderable::Private& Graph_Renderable::data_for_test() {
return static_cast<Private&>(*d);
}