This commit is contained in:
2026-09-01 10:40:08 +08:00
parent 9de411f623
commit 2fd60398c5
11 changed files with 469 additions and 261 deletions
@@ -0,0 +1,127 @@
#include "Frame_Policy.hpp"
#include <algorithm>
#include <cmath>
namespace aethera {
namespace {
std::optional<double> capacity_fps(
const Sliding_Statistics& statistics) noexcept {
const double average = statistics.summary().average;
if (!std::isfinite(average) || average <= 0.0) {
return std::nullopt;
}
return 1'000'000'000.0 / average;
}
}
Frame_Policy::Private::Active_Statistics::Active_Statistics(
std::size_t window_size) :
render(window_size),
send(window_size),
end_to_end(window_size) {}
bool Frame_Policy::Private::prepare_run(
const Prop& properties,
Export_Struct<State>& published_state) {
const bool timing_required =
properties.statistics_enabled ||
properties.render_rate_limit_enabled ||
properties.send_rate_limit_enabled;
if (timing_required && properties.statistics_window_size == 0) {
return false;
}
statistics.reset();
user_rate_limit = properties.user_frames_per_second;
render_rate_limit_enabled = properties.render_rate_limit_enabled;
send_rate_limit_enabled = properties.send_rate_limit_enabled;
statistics_enabled = properties.statistics_enabled;
timer_ticks = 0;
dropped_timer_ticks = 0;
completed_frames = 0;
if (timing_required) {
statistics.emplace(properties.statistics_window_size);
}
publish(published_state);
return true;
}
void Frame_Policy::Private::record_timer_tick(
bool dropped,
Export_Struct<State>& published_state) {
++timer_ticks;
if (dropped) {
++dropped_timer_ticks;
}
publish(published_state);
}
void Frame_Policy::Private::record_render(
std::chrono::nanoseconds duration,
Export_Struct<State>& published_state) {
if (statistics) {
statistics->render.add(static_cast<double>(duration.count()));
}
publish(published_state);
}
void Frame_Policy::Private::record_send(
std::chrono::nanoseconds send_duration,
std::chrono::nanoseconds end_to_end_duration,
Export_Struct<State>& published_state) {
if (statistics) {
statistics->send.add(static_cast<double>(send_duration.count()));
statistics->end_to_end.add(
static_cast<double>(end_to_end_duration.count()));
}
++completed_frames;
publish(published_state);
}
double Frame_Policy::Private::effective_frames_per_second() const noexcept {
double effective = user_rate_limit.value_or(
Default_Frames_Per_Second);
bool has_limit = user_rate_limit.has_value();
if (statistics && render_rate_limit_enabled) {
if (const auto capacity = capacity_fps(statistics->render)) {
effective = has_limit ? std::min(effective, *capacity) : *capacity;
has_limit = true;
}
}
if (statistics && send_rate_limit_enabled) {
if (const auto capacity = capacity_fps(statistics->send)) {
effective = has_limit ? std::min(effective, *capacity) : *capacity;
has_limit = true;
}
}
return has_limit ? effective : Default_Frames_Per_Second;
}
void Frame_Policy::Private::publish(
Export_Struct<State>& published_state) {
State& output = *published_state.internal.use();
output.timer_ticks = timer_ticks;
output.dropped_timer_ticks = dropped_timer_ticks;
output.completed_frames = completed_frames;
output.render_capacity_fps =
statistics && render_rate_limit_enabled
? capacity_fps(statistics->render)
: std::nullopt;
output.send_capacity_fps =
statistics && send_rate_limit_enabled
? capacity_fps(statistics->send)
: std::nullopt;
output.effective_frames_per_second = effective_frames_per_second();
output.render_time_ns = statistics && statistics_enabled
? statistics->render.summary()
: Statistics_Summary{};
output.send_time_ns = statistics && statistics_enabled
? statistics->send.summary()
: Statistics_Summary{};
output.end_to_end_time_ns = statistics && statistics_enabled
? statistics->end_to_end.summary()
: Statistics_Summary{};
published_state.internal.advance();
}
}
@@ -10,65 +10,33 @@
#include <optional>
namespace aethera {
/* 所有帧策略共享的统计配置、公开状态和统计实现,不拥有具体调度状态。 */
template <typename Self>
/* 所有帧策略共享的自适应限速配置、公开状态和统计实现,不拥有具体调度状态。 */
struct Frame_Policy : Model_Layer<
Self,
Root<Self>,
Storage_Registration<Prop_Tag, Import_Struct_With_Dirty>> {
using Layer = Model_Layer<
Self,
Root<Self>,
Storage_Registration<Prop_Tag, Import_Struct_With_Dirty>>;
using Prev_Private = typename Layer::Prev_Private;
using Prev_Builder = typename Layer::Prev_Builder;
Root,
Storage_Registration<Prop_Tag, Import_Struct_With_Dirty>,
Storage_Registration<State_Tag, Export_Struct>> {
static constexpr double Default_Frames_Per_Second{30.0};
struct Prop : Layer::template Prev<Prop_Tag> {
bool statistics_enabled{}; /* true 时采集并发布滑动统计。 */
std::size_t statistics_window_size{120}; /* 启用统计时的样本窗口容量。 */
struct Prop : Prev<Prop_Tag> {
std::optional<double> user_frames_per_second; /* 有值时作为用户帧率上限;无值时不参与限速。 */
bool render_rate_limit_enabled{}; /* true 时用渲染平均耗时推导理论生成上限。 */
bool send_rate_limit_enabled{}; /* true 时用发送平均耗时推导理论发送上限。 */
bool statistics_enabled{}; /* true 时对外发布完整滑动统计。 */
std::size_t statistics_window_size{120}; /* 统计或理论限速启用时的样本窗口容量。 */
};
struct State {
std::uint64_t timer_ticks{}; /* 已处理的周期到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 到期时三帧均忙而丢弃的次数。 */
std::uint64_t completed_frames{}; /* 已完成媒体发送的帧数。 */
Statistics_Summary render_time_ns; /* 渲染耗时滑动统计,单位纳秒。 */
Statistics_Summary send_time_ns; /* 发送耗时滑动统计,单位纳秒。 */
Statistics_Summary end_to_end_time_ns; /* 渲染开始至发送完成耗时统计,单位纳秒。 */
};
struct Private : Prev_Private {
struct Active_Statistics {
explicit Active_Statistics(std::size_t window_size);
Sliding_Statistics render; /* 渲染耗时窗口。 */
Sliding_Statistics send; /* 发送耗时窗口。 */
Sliding_Statistics end_to_end; /* 渲染开始至发送结束耗时窗口。 */
};
bool prepare_statistics(
const Prop& properties,
Export_Struct<State>& published_state);
void record_timer_tick(
bool dropped,
Export_Struct<State>& published_state);
void record_render(
std::chrono::nanoseconds duration,
Export_Struct<State>& published_state);
void record_send(
std::chrono::nanoseconds send_duration,
std::chrono::nanoseconds end_to_end_duration,
Export_Struct<State>& published_state);
private:
void publish(Export_Struct<State>& published_state);
std::optional<Active_Statistics> statistics; /* 存在即表示本次运行启用了统计。 */
std::uint64_t timer_ticks{}; /* 本次运行累计的到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 本次运行累计的忙碌丢弃次数。 */
std::uint64_t completed_frames{}; /* 本次运行累计的完成帧数。 */
struct State : Prev<State_Tag> {
std::uint64_t timer_ticks{}; /* 已处理的调度到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 到期时三帧均忙而丢弃的次数。 */
std::uint64_t completed_frames{}; /* 已完成媒体发送的帧数。 */
std::optional<double> render_capacity_fps; /* 渲染样本时推导出的理论最大生成帧率。 */
std::optional<double> send_capacity_fps; /* 发送样本时推导出的理论最大发送帧率。 */
double effective_frames_per_second{Default_Frames_Per_Second}; /* 当前有效限速来源的最小值。 */
Statistics_Summary render_time_ns; /* 渲染耗时滑动统计,单位纳秒。 */
Statistics_Summary send_time_ns; /* 发送耗时滑动统计,单位纳秒。 */
Statistics_Summary end_to_end_time_ns; /* 渲染开始至发送完成耗时统计,单位纳秒。 */
};
struct Private;
struct Builder : Prev_Builder {};
Export_Struct<State> state; /* 由策略互斥串行发布的帧策略运行统计。 */
};
}
@@ -1,88 +1,40 @@
#pragma once
namespace aethera {
template <typename Self>
Frame_Policy<Self>::Private::Active_Statistics::Active_Statistics(
std::size_t window_size) :
render(window_size),
send(window_size),
end_to_end(window_size) {}
struct Frame_Policy::Private : Prev_Private {
struct Active_Statistics {
explicit Active_Statistics(std::size_t window_size);
template <typename Self>
bool Frame_Policy<Self>::Private::prepare_statistics(
const Prop& properties,
Export_Struct<State>& published_state) {
if (properties.statistics_enabled && properties.statistics_window_size == 0) {
return false;
}
Sliding_Statistics render; /* 渲染耗时窗口。 */
Sliding_Statistics send; /* 发送耗时窗口。 */
Sliding_Statistics end_to_end; /* 渲染开始至发送结束耗时窗口。 */
};
statistics.reset();
timer_ticks = 0;
dropped_timer_ticks = 0;
completed_frames = 0;
if (properties.statistics_enabled) {
statistics.emplace(properties.statistics_window_size);
}
publish(published_state);
return true;
}
bool prepare_run(
const Prop& properties,
Export_Struct<State>& published_state);
void record_timer_tick(
bool dropped,
Export_Struct<State>& published_state);
void record_render(
std::chrono::nanoseconds duration,
Export_Struct<State>& published_state);
void record_send(
std::chrono::nanoseconds send_duration,
std::chrono::nanoseconds end_to_end_duration,
Export_Struct<State>& published_state);
[[nodiscard]] double effective_frames_per_second() const noexcept;
template <typename Self>
void Frame_Policy<Self>::Private::record_timer_tick(
bool dropped,
Export_Struct<State>& published_state) {
if (!statistics) {
return;
}
++timer_ticks;
if (dropped) {
++dropped_timer_ticks;
}
publish(published_state);
}
private:
void publish(Export_Struct<State>& published_state);
template <typename Self>
void Frame_Policy<Self>::Private::record_render(
std::chrono::nanoseconds duration,
Export_Struct<State>& published_state) {
if (!statistics) {
return;
}
statistics->render.add(static_cast<double>(duration.count()));
publish(published_state);
}
template <typename Self>
void Frame_Policy<Self>::Private::record_send(
std::chrono::nanoseconds send_duration,
std::chrono::nanoseconds end_to_end_duration,
Export_Struct<State>& published_state) {
if (!statistics) {
return;
}
statistics->send.add(static_cast<double>(send_duration.count()));
statistics->end_to_end.add(
static_cast<double>(end_to_end_duration.count()));
++completed_frames;
publish(published_state);
}
template <typename Self>
void Frame_Policy<Self>::Private::publish(
Export_Struct<State>& published_state) {
State& output = *published_state.internal.use();
output.timer_ticks = timer_ticks;
output.dropped_timer_ticks = dropped_timer_ticks;
output.completed_frames = completed_frames;
output.render_time_ns = statistics
? statistics->render.summary()
: Statistics_Summary{};
output.send_time_ns = statistics
? statistics->send.summary()
: Statistics_Summary{};
output.end_to_end_time_ns = statistics
? statistics->end_to_end.summary()
: Statistics_Summary{};
published_state.internal.advance();
}
std::optional<Active_Statistics> statistics; /* 存在即表示本次运行需要耗时窗口。 */
std::optional<double> user_rate_limit; /* 当前运行采用的用户帧率上限。 */
bool render_rate_limit_enabled{}; /* 当前运行是否采用渲染理论上限。 */
bool send_rate_limit_enabled{}; /* 当前运行是否采用发送理论上限。 */
bool statistics_enabled{}; /* 当前运行是否向 State 发布完整统计。 */
std::uint64_t timer_ticks{}; /* 本次运行累计的到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 本次运行累计的忙碌丢弃次数。 */
std::uint64_t completed_frames{}; /* 本次运行累计的完成帧数。 */
};
}
@@ -35,7 +35,7 @@ std::expected<std::chrono::nanoseconds, Throttled_Latest_only::Start_Result>
frame_interval(double frames_per_second) noexcept {
using Start_Result = Throttled_Latest_only::Start_Result;
if (!std::isfinite(frames_per_second) || frames_per_second <= 0.0) {
return std::unexpected(Start_Result::invalid_frames_per_second);
return std::unexpected(Start_Result::invalid_user_frame_rate);
}
constexpr long double Nanoseconds_Per_Second = 1'000'000'000.0L;
@@ -51,10 +51,17 @@ frame_interval(double frames_per_second) noexcept {
static_cast<std::chrono::nanoseconds::rep>(
std::max(1.0L, std::ceil(interval)))};
}
std::chrono::nanoseconds positive_duration(
std::chrono::steady_clock::duration duration) noexcept {
return std::max(
std::chrono::nanoseconds{1},
std::chrono::duration_cast<std::chrono::nanoseconds>(duration));
}
}
Throttled_Latest_only::Throttled_Latest_only(
proxy<Periodic_Timer_Service> timer_service,
proxy<Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink) :
Def(std::move(timer_service), std::move(scene), std::move(sink)) {}
@@ -66,11 +73,11 @@ Throttled_Latest_only::~Throttled_Latest_only() noexcept {
}
Throttled_Latest_only::Start_Result
Throttled_Latest_only::start(double frames_per_second) {
Throttled_Latest_only::start() {
auto& properties_storage = d->get<Prop_Tag>();
properties_storage.internal.advance();
const Prop& properties = *properties_storage.internal.use();
return d->start(properties, state, frames_per_second);
return d->start(properties, d->get<State_Tag>());
}
Throttled_Latest_only::Stop_Result
@@ -79,7 +86,7 @@ Throttled_Latest_only::stop(Stop_Completion completion) {
}
Throttled_Latest_only::Private::Private(
proxy<Periodic_Timer_Service> timer_service,
proxy<Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink) :
timer_service(std::move(timer_service)),
@@ -89,9 +96,10 @@ Throttled_Latest_only::Private::Private(
Throttled_Latest_only::Start_Result
Throttled_Latest_only::Private::start(
const Prop& properties,
Export_Struct<State>& published_state,
double frames_per_second) {
const auto interval = frame_interval(frames_per_second);
Export_Struct<State>& published_state) {
const auto interval = frame_interval(
properties.user_frames_per_second.value_or(
Frame_Policy::Default_Frames_Per_Second));
if (!interval) {
return interval.error();
}
@@ -177,7 +185,7 @@ Throttled_Latest_only::Private::prepare_start(
if (!timer_service || !scene || !sink) {
return Start_Result::dependency_unavailable;
}
if (!prepare_statistics(properties, published_state)) {
if (!prepare_run(properties, published_state)) {
return Start_Result::invalid_statistics_window;
}
phase = Phase::starting;
@@ -282,13 +290,17 @@ void Throttled_Latest_only::Private::frame_due(
}
}
record_timer_tick(selected_index == Frame_Count, published_state);
if (selected_index == Frame_Count) {
return;
if (selected_index != Frame_Count) {
Frame_Slot& slot = frames[selected_index];
slot.phase = Frame_Phase::rendering;
slot.render_start = std::chrono::steady_clock::now();
next_frame_index = (selected_index + 1) % Frame_Count;
}
Frame_Slot& slot = frames[selected_index];
slot.phase = Frame_Phase::rendering;
slot.render_start = std::chrono::steady_clock::now();
next_frame_index = (selected_index + 1) % Frame_Count;
}
if (selected_index == Frame_Count) {
reschedule_next_frame();
return;
}
try {
@@ -301,8 +313,29 @@ void Throttled_Latest_only::Private::frame_due(
}
catch (...) {
abandon_render(selected_index);
reschedule_next_frame();
throw;
}
reschedule_next_frame();
}
void Throttled_Latest_only::Private::reschedule_next_frame() {
Timer_Id scheduled_timer_id{};
std::chrono::nanoseconds interval{};
{
std::lock_guard lock(mutex);
if (phase != Phase::running || !timer_id) {
return;
}
scheduled_timer_id = *timer_id;
const auto calculated = frame_interval(
effective_frames_per_second());
if (!calculated) {
std::terminate();
}
interval = *calculated;
}
timer_service->reschedule(scheduled_timer_id, interval);
}
void Throttled_Latest_only::Private::rendered(
@@ -321,8 +354,7 @@ void Throttled_Latest_only::Private::rendered(
}
slot.render_end = std::chrono::steady_clock::now();
record_render(
std::chrono::duration_cast<std::chrono::nanoseconds>(
slot.render_end - slot.render_start),
positive_duration(slot.render_end - slot.render_start),
published_state);
slot.phase = Frame_Phase::sending;
slot.send_start = std::chrono::steady_clock::now();
@@ -357,10 +389,8 @@ void Throttled_Latest_only::Private::sent(
}
slot.send_end = std::chrono::steady_clock::now();
record_send(
std::chrono::duration_cast<std::chrono::nanoseconds>(
slot.send_end - slot.send_start),
std::chrono::duration_cast<std::chrono::nanoseconds>(
slot.send_end - slot.render_start),
positive_duration(slot.send_end - slot.send_start),
positive_duration(slot.send_end - slot.render_start),
published_state);
slot.phase = Frame_Phase::idle;
}
@@ -5,11 +5,11 @@
#include <functional>
namespace aethera {
/*
* 固定帧率、最多三帧在途的 latest-only 策略。Scene 创建三个具体帧;策略轮转复用,
* 自适应限速、最多三帧在途的 latest-only 策略。Scene 创建三个具体帧;策略轮转复用,
* 三槽均忙时丢弃本次 tickstop completion 前排空 Scene/Sink 借用并销毁全部帧。
*/
struct Throttled_Latest_only :
Def<Throttled_Latest_only, Frame_Policy<Throttled_Latest_only>> {
Def<Throttled_Latest_only, Frame_Policy> {
enum struct Start_Result : std::uint8_t {
started,
already_running,
@@ -17,7 +17,7 @@ Def<Throttled_Latest_only, Frame_Policy<Throttled_Latest_only>> {
stop_in_progress,
dependency_unavailable,
frame_unavailable,
invalid_frames_per_second,
invalid_user_frame_rate,
interval_out_of_range,
invalid_statistics_window
};
@@ -31,11 +31,11 @@ Def<Throttled_Latest_only, Frame_Policy<Throttled_Latest_only>> {
using Stop_Completion = std::function<void()>;
struct Prop : Prev<Prop_Tag> {};
struct Private;
Throttled_Latest_only(proxy<Periodic_Timer_Service> timer_service,
Throttled_Latest_only(proxy<Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink);
~Throttled_Latest_only() noexcept;
Start_Result start(double frames_per_second);
Start_Result start();
Stop_Result stop(Stop_Completion completion);
};
}
@@ -29,14 +29,13 @@ struct Throttled_Latest_only::Private : Prev_Private {
static constexpr std::size_t Frame_Count{3};
Private(proxy<Periodic_Timer_Service> timer_service,
Private(proxy<Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink);
Start_Result start(
const Prop& properties,
Export_Struct<State>& published_state,
double frames_per_second);
Export_Struct<State>& published_state);
Stop_Result stop(Stop_Completion completion);
[[nodiscard]] bool destructible() const noexcept;
@@ -49,6 +48,7 @@ private:
void cancel_failed();
void timer_cancelled();
void frame_due(Export_Struct<State>& published_state);
void reschedule_next_frame();
void rendered(
std::size_t slot_index,
Export_Struct<State>& published_state,
@@ -61,7 +61,7 @@ private:
void abandon_send(std::size_t slot_index);
void finish_stop_if_ready();
proxy<Periodic_Timer_Service> timer_service; /* 周期调度和异步取消的必填业务能力所有权。 */
proxy<Timer_Service> timer_service; /* 动态重设期限和异步取消的必填业务能力所有权。 */
proxy<FP_Scene> scene; /* 帧池创建及渲染借用的必填业务能力所有权。 */
proxy<FP_Sink> sink; /* 已渲染帧媒体分派的必填业务能力所有权。 */
std::array<Frame_Slot, Frame_Count> frames; /* 固定三个槽位的轮转帧池。 */
+26 -52
View File
@@ -1,120 +1,94 @@
#pragma once
#include "../concurrent/Concurrent_Storage_with_dirty.hpp"
#include "../concurrent/storage/Tagged_Storage.hpp"
#include <memory>
#include <type_traits>
namespace aethera {
namespace detail {
template <typename... Types>
struct Model_Type_List {};
template <typename List, typename... Registrations>
struct Append_Model_Registrations;
template <typename Tag, typename List>
struct Model_Tag_Registered;
template <typename Tag, typename Base, bool Registered>
struct Previous_Model_Value;
template <typename... Types> struct Model_Type_List {};
template <typename List, typename... Registrations> struct Append_Model_Registrations;
template <typename Tag, typename List> struct Model_Tag_Registered;
template <typename Tag, typename Base, bool Registered> struct Previous_Model_Value;
struct Empty_Prop {};
struct Empty_State {};
}
/* Tag 负责选择各定义层的 Prop 类型;同一个 Tag 在注册表中只允许出现一次。 */
struct Prop_Tag {
using Root_Type = detail::Empty_Prop;
template <typename Layer>
using Value = typename Layer::Prop;
template <typename Layer> using Value = typename Layer::Prop;
};
/* Tag 负责选择各定义层的 State 类型。 */
struct State_Tag {
using Root_Type = detail::Empty_State;
template <typename Layer>
using Value = typename Layer::State;
template <typename Layer> using Value = typename Layer::State;
};
/* 把一个业务 Tag 注册到接受单个值类型的 Concurrent Storage 模板。 */
template <typename Tag, template <typename...> typename Concurrent_Storage>
struct Storage_Registration {
template <typename Tag, template <typename...> typename Concurrent_Storage> struct Storage_Registration {
using Tag_Type = Tag;
template <typename Value>
using Storage = Concurrent_Storage<Value>;
template <typename Value> using Storage = Concurrent_Storage<Value>;
};
template <typename Self>
struct Root {
using Storage_Registrations = detail::Model_Type_List<>;
template <typename Tag>
using Prev = typename Tag::Root_Type;
template <typename Tag> using Prev = typename Tag::Root_Type;
struct Private {};
struct Builder {};
};
/*
* 只扩展类型与存储注册链,不创建运行时 d;供多个业务定义层共同组成最终 Def。
*/
template <typename Self, typename Base, typename... Registrations>
struct Model_Layer : Base {
template <typename Base, typename... Registrations> struct Model_Layer : Base {
using Storage_Registrations = typename detail::Append_Model_Registrations<
typename Base::Storage_Registrations,
Registrations...>::Type;
template <typename Tag>
using Prev = typename detail::Previous_Model_Value<
template <typename Tag> using Prev = typename detail::Previous_Model_Value<
Tag,
Base,
detail::Model_Tag_Registered<
Tag,
typename Base::Storage_Registrations>::value>::Type;
using Prev_Private = typename Base::Private;
using Prev_Builder = typename Base::Builder;
};
/* 最终定义层根据完整注册表构造唯一 Private 与全部 Tagged Storage。 */
template <typename Self, typename Base, typename... Registrations>
struct Def : Model_Layer<Self, Base, Registrations...> {
using Layer = Model_Layer<Self, Base, Registrations...>;
template <typename Self, typename Base, typename... Registrations> struct Def : Model_Layer<Base, Registrations...> {
using Layer = Model_Layer<Base, Registrations...>;
using Storage_Registrations = typename Layer::Storage_Registrations;
using Prev_Private = typename Base::Private;
struct Private;
struct Builder : Base::Builder {
template <typename... Args>
explicit Builder(Args&&... args);
template <typename... Args> explicit Builder(Args&&... args);
std::unique_ptr<Self> build();
template <
template <typename...> typename Concurrent_Storage,
typename Tag,
typename... Args>
requires requires(
typename... Args> requires requires(
Concurrent_Storage<typename Tag::template Value<Self>>& storage,
Args&&... args) {
storage.write(std::forward<Args>(args)...);
}
Builder& set(Args&&... args);
std::unique_ptr<Self> ret; /* 构建期间唯一拥有尚未发布的对象。 */
};
template <
template <typename...> typename Concurrent_Storage,
typename Tag,
typename... Args>
requires requires(
typename... Args> requires requires(
Concurrent_Storage<typename Tag::template Value<Self>>& storage,
Args&&... args) {
storage.write(std::forward<Args>(args)...);
}
Self& write(Args&&... args);
template <typename... Args>
explicit Def(Args&&... args);
template <
template <typename...> typename Concurrent_Storage,
typename Tag,
typename... Args> requires requires(
const Concurrent_Storage<typename Tag::template Value<Self>>& storage,
Args&&... args) {
storage.read(std::forward<Args>(args)...);
}
const Self& read(Args&&... args) const;
template <typename... Args> explicit Def(Args&&... args);
std::unique_ptr<Private> d; /* 唯一拥有最终 Private 和完整 Tagged Storage 集。 */
};
}
#include "Model.ipp"
+21
View File
@@ -162,4 +162,25 @@ Self& Def<Self, Base, Registrations...>::write(Args&&... args) {
storage.write(std::forward<Args>(args)...);
return static_cast<Self&>(*this);
}
template <typename Self, typename Base, typename... Registrations>
template <
template <typename...> typename Concurrent_Storage,
typename Tag,
typename... Args>
requires requires(
const Concurrent_Storage<typename Tag::template Value<Self>>& storage,
Args&&... args) {
storage.read(std::forward<Args>(args)...);
}
const Self& Def<Self, Base, Registrations...>::read(Args&&... args) const {
const auto& storage = d->template get<Tag>();
using Value = typename Tag::template Value<Self>;
static_assert(std::same_as<
std::remove_cvref_t<decltype(storage)>,
Concurrent_Storage<Value>>,
"the concurrent storage and tag must name one registration");
storage.read(std::forward<Args>(args)...);
return static_cast<const Self&>(*this);
}
}
+1 -1
View File
@@ -7,6 +7,6 @@
`Model_Layer` 通过 `Storage_Registration<Tag, Concurrent_Storage>` 注册一条业务值链。每个定义层用 `Prev<Tag>` 取得前一层值类型并继承;最终 `Def::Private` 根据完整注册表只创建一次 `Tagged_Storage_Set`。同一 Tag 重复注册属于编译期错误。
Builder 使用 `set<Concurrent_Storage, Tag>(...)`,最终模型使用 `write<Concurrent_Storage, Tag>(...)`。两个模板参数必须命中同一注册项,参数能力直接由对应并发存储的 `write(...)` 重载约束:Struct 可以整对象或按成员写,List 只接受列表批次写入。Builder 对 Struct 同时建立内部读面和外部写面且不产生 dirty;其他存储保留自身写入/消费语义。
Builder 使用 `set<Concurrent_Storage, Tag>(...)`,最终模型使用 `write<Concurrent_Storage, Tag>(...)``read<Concurrent_Storage, Tag>(...)`。两个模板参数必须命中同一注册项,参数能力直接由对应并发存储的 `write/read(...)` 重载约束:Struct 可以整对象或按成员写,List 保留自身批次语义。Builder 对 Struct 同时建立内部读面和外部写面且不产生 dirty;其他存储保留自身写入/消费语义。
属性 binder 只在对应 Tag 存储的 `internal.advance()` 之后、同一内部线程阶段查询。外部写入、`advance()` 和 binder 查询按阶段串行且不重叠,因此 dirty revision 与属性表不使用锁或原子。
@@ -3,11 +3,13 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <chrono>
#include <deque>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
@@ -53,7 +55,8 @@ struct Frame_Policy_Test_Control {
rendered_frame_addresses; /* 可空、非拥有;记录每次渲染采用的槽位地址。 */
aethera::Timer_Callback timer_callback; /* 周期服务持有的到期回调。 */
aethera::Timer_Callback cancel_completion; /* 测试显式完成异步取消的回调。 */
std::chrono::nanoseconds scheduled_interval{}; /* 策略提交给服务的周期。 */
std::chrono::nanoseconds scheduled_interval{}; /* 策略提交给服务的初始期限。 */
std::chrono::nanoseconds rescheduled_interval{}; /* 策略最近一次计算出的下一帧延迟。 */
aethera::Timer_Id cancelled_timer_id{}; /* 策略请求取消的计时器标识。 */
std::size_t frames_created{}; /* Scene 创建的物理帧数量。 */
std::size_t frames_alive{}; /* 尚未析构的物理帧数量。 */
@@ -65,6 +68,14 @@ struct Frame_Policy_Test_Control {
};
struct Test_Timer_Service {
aethera::Timer_Id schedule_after(
std::chrono::nanoseconds interval,
aethera::Timer_Callback callback) {
control->scheduled_interval = interval;
control->timer_callback = std::move(callback);
return Timer_Id;
}
aethera::Timer_Id schedule_every(
std::chrono::nanoseconds interval,
aethera::Timer_Callback callback) {
@@ -80,6 +91,13 @@ struct Test_Timer_Service {
control->cancel_completion = std::move(completion);
}
void reschedule(
aethera::Timer_Id id,
std::chrono::nanoseconds interval) {
EXPECT_EQ(id, Timer_Id);
control->rescheduled_interval = interval;
}
static constexpr aethera::Timer_Id Timer_Id{7};
std::shared_ptr<Frame_Policy_Test_Control> control; /* 服务调用记录的共享所有权。 */
};
@@ -151,17 +169,29 @@ struct Test_Sink {
std::unique_ptr<aethera::Throttled_Latest_only> make_policy(
const std::shared_ptr<Frame_Policy_Test_Control>& control,
std::optional<double> user_frames_per_second = std::nullopt,
bool render_rate_limit_enabled = false,
bool send_rate_limit_enabled = false,
bool statistics_enabled = false,
std::size_t statistics_window_size = 120) {
auto timer_service =
pro::make_proxy<
aethera::Periodic_Timer_Service,
aethera::Timer_Service,
Test_Timer_Service>(control);
auto scene = pro::make_proxy<aethera::FP_Scene, Test_Scene>(control);
auto sink = pro::make_proxy<aethera::FP_Sink, Test_Sink>(control);
aethera::Throttled_Latest_only::Builder builder(
std::move(timer_service), std::move(scene), std::move(sink));
builder
.set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(
&aethera::Throttled_Latest_only::Prop::user_frames_per_second,
user_frames_per_second)
.set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(
&aethera::Throttled_Latest_only::Prop::render_rate_limit_enabled,
render_rate_limit_enabled)
.set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(
&aethera::Throttled_Latest_only::Prop::send_rate_limit_enabled,
send_rate_limit_enabled)
.set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(
&aethera::Throttled_Latest_only::Prop::statistics_enabled,
statistics_enabled)
@@ -192,9 +222,9 @@ TEST(Sliding_Statistics, Maintains_Average_P95_And_Deviation_Without_Snapshot) {
TEST(Throttled_Latest_Only, Rotates_Three_Frames_And_Drops_When_All_Are_Busy) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control, true);
auto policy = make_policy(control, 50.0, false, false, true);
ASSERT_EQ(
policy->start(50.0),
policy->start(),
aethera::Throttled_Latest_only::Start_Result::started);
EXPECT_EQ(control->scheduled_interval, 20ms);
EXPECT_EQ(control->frames_created, 3);
@@ -227,11 +257,12 @@ TEST(Throttled_Latest_Only, Rotates_Three_Frames_And_Drops_When_All_Are_Busy) {
control->complete_send();
}
policy->state.read([](const aethera::Throttled_Latest_only::State& state) {
policy->read<aethera::Export_Struct, aethera::State_Tag>(
[](const aethera::Throttled_Latest_only::State& state) {
EXPECT_EQ(state.timer_ticks, 6);
EXPECT_EQ(state.dropped_timer_ticks, 2);
EXPECT_EQ(state.completed_frames, 4);
});
});
bool stopped = false;
EXPECT_EQ(
@@ -246,9 +277,9 @@ TEST(Throttled_Latest_Only, Rotates_Three_Frames_And_Drops_When_All_Are_Busy) {
TEST(Throttled_Latest_Only, Stop_Drains_All_Frame_Borrows) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control);
auto policy = make_policy(control, 60.0);
ASSERT_EQ(
policy->start(60.0),
policy->start(),
aethera::Throttled_Latest_only::Start_Result::started);
control->fire_timer();
control->fire_timer();
@@ -271,26 +302,124 @@ TEST(Throttled_Latest_Only, Stop_Drains_All_Frame_Borrows) {
control->complete_send();
EXPECT_TRUE(stopped);
EXPECT_EQ(control->frames_alive, 0);
policy->state.read([](const aethera::Throttled_Latest_only::State& state) {
EXPECT_EQ(state.timer_ticks, 0);
EXPECT_EQ(state.completed_frames, 0);
});
policy->read<aethera::Export_Struct, aethera::State_Tag>(
[](const aethera::Throttled_Latest_only::State& state) {
EXPECT_EQ(state.timer_ticks, 2);
EXPECT_EQ(state.completed_frames, 2);
});
}
TEST(Throttled_Latest_Only, Sending_Frame_Does_Not_Block_Next_Render) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control, 60.0);
ASSERT_EQ(
policy->start(),
aethera::Throttled_Latest_only::Start_Result::started);
control->fire_timer();
control->complete_render();
ASSERT_EQ(control->sending_frames.size(), 1);
control->fire_timer();
EXPECT_EQ(control->sending_frames.size(), 1);
EXPECT_EQ(control->rendering_frames.size(), 1);
EXPECT_EQ(control->render_calls, 2);
control->complete_render();
control->complete_send();
control->complete_send();
bool stopped = false;
ASSERT_EQ(
policy->stop([&stopped] {
stopped = true;
}),
aethera::Throttled_Latest_only::Stop_Result::stopping);
control->complete_cancel();
EXPECT_TRUE(stopped);
}
TEST(Throttled_Latest_Only, Defaults_To_Thirty_Frames_Per_Second) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control);
ASSERT_EQ(
policy->start(),
aethera::Throttled_Latest_only::Start_Result::started);
EXPECT_EQ(control->scheduled_interval, 33333334ns);
control->fire_timer();
EXPECT_EQ(control->rescheduled_interval, 33333334ns);
control->complete_render();
control->complete_send();
policy->read<aethera::Export_Struct, aethera::State_Tag>(
[](const aethera::Throttled_Latest_only::State& state) {
EXPECT_DOUBLE_EQ(state.effective_frames_per_second, 30.0);
EXPECT_FALSE(state.render_capacity_fps.has_value());
EXPECT_FALSE(state.send_capacity_fps.has_value());
});
bool stopped = false;
ASSERT_EQ(
policy->stop([&stopped] {
stopped = true;
}),
aethera::Throttled_Latest_only::Stop_Result::stopping);
control->complete_cancel();
EXPECT_TRUE(stopped);
}
TEST(Throttled_Latest_Only, Combines_Optional_Adaptive_Rate_Limits) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control, std::nullopt, true, true);
ASSERT_EQ(
policy->start(),
aethera::Throttled_Latest_only::Start_Result::started);
EXPECT_EQ(control->scheduled_interval, 33333334ns);
control->fire_timer();
EXPECT_EQ(control->rescheduled_interval, 33333334ns);
control->complete_render();
control->complete_send();
control->fire_timer();
policy->read<aethera::Export_Struct, aethera::State_Tag>(
[](const aethera::Throttled_Latest_only::State& state) {
ASSERT_TRUE(state.render_capacity_fps.has_value());
ASSERT_TRUE(state.send_capacity_fps.has_value());
EXPECT_DOUBLE_EQ(
state.effective_frames_per_second,
std::min(
*state.render_capacity_fps,
*state.send_capacity_fps));
});
EXPECT_GT(control->rescheduled_interval, 0ns);
control->complete_render();
control->complete_send();
bool stopped = false;
ASSERT_EQ(
policy->stop([&stopped] {
stopped = true;
}),
aethera::Throttled_Latest_only::Stop_Result::stopping);
control->complete_cancel();
EXPECT_TRUE(stopped);
}
TEST(Throttled_Latest_Only, Publishes_Optional_Sliding_Statistics) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
control->complete_render_synchronously = true;
control->complete_send_synchronously = true;
auto policy = make_policy(control, true, 2);
auto policy = make_policy(control, 60.0, false, false, true, 2);
ASSERT_EQ(
policy->start(60.0),
policy->start(),
aethera::Throttled_Latest_only::Start_Result::started);
control->fire_timer();
control->fire_timer();
control->fire_timer();
policy->state.read([](const aethera::Throttled_Latest_only::State& state) {
policy->read<aethera::Export_Struct, aethera::State_Tag>(
[](const aethera::Throttled_Latest_only::State& state) {
EXPECT_EQ(state.timer_ticks, 3);
EXPECT_EQ(state.dropped_timer_ticks, 0);
EXPECT_EQ(state.completed_frames, 3);
@@ -299,7 +428,7 @@ TEST(Throttled_Latest_Only, Publishes_Optional_Sliding_Statistics) {
EXPECT_EQ(state.end_to_end_time_ns.sample_count, 2);
EXPECT_GE(state.render_time_ns.p95, 0.0);
EXPECT_GE(state.end_to_end_time_ns.standard_deviation, 0.0);
});
});
bool stopped = false;
ASSERT_EQ(
@@ -313,29 +442,48 @@ TEST(Throttled_Latest_Only, Publishes_Optional_Sliding_Statistics) {
TEST(Throttled_Latest_Only, Reports_Invalid_Configuration) {
aethera::Throttled_Latest_only::Builder missing_builder(
aethera::proxy<aethera::Periodic_Timer_Service>{},
aethera::proxy<aethera::Timer_Service>{},
aethera::proxy<aethera::FP_Scene>{},
aethera::proxy<aethera::FP_Sink>{});
auto missing_dependencies = missing_builder.build();
EXPECT_EQ(
missing_dependencies->start(60.0),
missing_dependencies->start(),
aethera::Throttled_Latest_only::Start_Result::dependency_unavailable);
EXPECT_EQ(
missing_dependencies->stop({}),
aethera::Throttled_Latest_only::Stop_Result::completion_missing);
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control);
auto policy = make_policy(control, 0.0);
EXPECT_EQ(
policy->start(0.0),
aethera::Throttled_Latest_only::Start_Result::invalid_frames_per_second);
policy->start(),
aethera::Throttled_Latest_only::Start_Result::invalid_user_frame_rate);
auto tiny_rate = make_policy(
control,
std::numeric_limits<double>::denorm_min());
EXPECT_EQ(
policy->start(std::numeric_limits<double>::denorm_min()),
tiny_rate->start(),
aethera::Throttled_Latest_only::Start_Result::interval_out_of_range);
auto invalid_statistics = make_policy(control, true, 0);
auto invalid_statistics = make_policy(
control,
60.0,
false,
false,
true,
0);
EXPECT_EQ(
invalid_statistics->start(60.0),
invalid_statistics->start(),
aethera::Throttled_Latest_only::Start_Result::invalid_statistics_window);
auto invalid_adaptive_window = make_policy(
control,
std::nullopt,
true,
false,
false,
0);
EXPECT_EQ(
invalid_adaptive_window->start(),
aethera::Throttled_Latest_only::Start_Result::invalid_statistics_window);
}
@@ -343,9 +491,9 @@ TEST(Throttled_Latest_Only, Synchronous_Completions_And_Failure_Restore_Slot) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
control->complete_render_synchronously = true;
control->throw_from_send = true;
auto policy = make_policy(control);
auto policy = make_policy(control, 60.0);
ASSERT_EQ(
policy->start(60.0),
policy->start(),
aethera::Throttled_Latest_only::Start_Result::started);
EXPECT_THROW(control->fire_timer(), std::runtime_error);
+6 -18
View File
@@ -15,33 +15,21 @@ struct Number_List_Tag {
using Value = typename Layer::Numbers;
};
template <typename Self>
struct Test_Model_Base : aethera::Model_Layer<
Self,
aethera::Root<Self>,
aethera::Root,
aethera::Storage_Registration<
aethera::Prop_Tag,
aethera::Import_Struct_With_Dirty>,
aethera::Storage_Registration<Number_List_Tag, aethera::Import_List>> {
using Layer = aethera::Model_Layer<
Self,
aethera::Root<Self>,
aethera::Storage_Registration<
aethera::Prop_Tag,
aethera::Import_Struct_With_Dirty>,
aethera::Storage_Registration<Number_List_Tag, aethera::Import_List>>;
using Prev_Private = typename Layer::Prev_Private;
using Prev_Builder = typename Layer::Prev_Builder;
struct Prop : Layer::template Prev<aethera::Prop_Tag> {
struct Prop : Prev<aethera::Prop_Tag> {
int base_number{};
};
struct Numbers : Layer::template Prev<Number_List_Tag> {};
struct Numbers : Prev<Number_List_Tag> {};
struct Private : Prev_Private {};
struct Builder : Prev_Builder {};
};
struct Test_Model : aethera::Def<Test_Model, Test_Model_Base<Test_Model>> {
struct Test_Model : aethera::Def<Test_Model, Test_Model_Base> {
struct Prop : Prev<aethera::Prop_Tag> {
int number{};
int other{};
@@ -52,10 +40,10 @@ struct Test_Model : aethera::Def<Test_Model, Test_Model_Base<Test_Model>> {
static_assert(std::derived_from<
Test_Model::Prop,
Test_Model_Base<Test_Model>::Prop>);
Test_Model_Base::Prop>);
static_assert(std::derived_from<
Test_Model::Numbers,
Test_Model_Base<Test_Model>::Numbers>);
Test_Model_Base::Numbers>);
TEST(Model, Registered_Tags_Build_Independent_Chained_Storages) {
Test_Model::Builder builder;