diff --git a/kernel/kernel/include/function/frame_policy/Frame_Policy.cpp b/kernel/kernel/include/function/frame_policy/Frame_Policy.cpp new file mode 100644 index 0000000..295b532 --- /dev/null +++ b/kernel/kernel/include/function/frame_policy/Frame_Policy.cpp @@ -0,0 +1,127 @@ +#include "Frame_Policy.hpp" + +#include +#include + +namespace aethera { +namespace { +std::optional 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& 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& published_state) { + ++timer_ticks; + if (dropped) { + ++dropped_timer_ticks; + } + publish(published_state); +} + +void Frame_Policy::Private::record_render( + std::chrono::nanoseconds duration, + Export_Struct& published_state) { + if (statistics) { + statistics->render.add(static_cast(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& published_state) { + if (statistics) { + statistics->send.add(static_cast(send_duration.count())); + statistics->end_to_end.add( + static_cast(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& 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(); +} +} diff --git a/kernel/kernel/include/function/frame_policy/Frame_Policy.hpp b/kernel/kernel/include/function/frame_policy/Frame_Policy.hpp index 142d69e..93b3636 100644 --- a/kernel/kernel/include/function/frame_policy/Frame_Policy.hpp +++ b/kernel/kernel/include/function/frame_policy/Frame_Policy.hpp @@ -10,65 +10,33 @@ #include namespace aethera { -/* 所有帧策略共享的统计配置、公开状态和统计实现,不拥有具体调度状态。 */ -template +/* 所有帧策略共享的自适应限速配置、公开状态和统计实现,不拥有具体调度状态。 */ struct Frame_Policy : Model_Layer< - Self, - Root, - Storage_Registration> { - using Layer = Model_Layer< - Self, - Root, - Storage_Registration>; - using Prev_Private = typename Layer::Prev_Private; - using Prev_Builder = typename Layer::Prev_Builder; + Root, + Storage_Registration, + Storage_Registration> { + static constexpr double Default_Frames_Per_Second{30.0}; - struct Prop : Layer::template Prev { - bool statistics_enabled{}; /* true 时采集并发布滑动统计。 */ - std::size_t statistics_window_size{120}; /* 启用统计时的样本窗口容量。 */ + struct Prop : Prev { + std::optional 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& published_state); - void record_timer_tick( - bool dropped, - Export_Struct& published_state); - void record_render( - std::chrono::nanoseconds duration, - Export_Struct& published_state); - void record_send( - std::chrono::nanoseconds send_duration, - std::chrono::nanoseconds end_to_end_duration, - Export_Struct& published_state); - - private: - void publish(Export_Struct& published_state); - - std::optional statistics; /* 存在即表示本次运行启用了统计。 */ - std::uint64_t timer_ticks{}; /* 本次运行累计的到期次数。 */ - std::uint64_t dropped_timer_ticks{}; /* 本次运行累计的忙碌丢弃次数。 */ - std::uint64_t completed_frames{}; /* 本次运行累计的完成帧数。 */ + struct State : Prev { + std::uint64_t timer_ticks{}; /* 已处理的调度到期次数。 */ + std::uint64_t dropped_timer_ticks{}; /* 到期时三帧均忙而丢弃的次数。 */ + std::uint64_t completed_frames{}; /* 已完成媒体发送的帧数。 */ + std::optional render_capacity_fps; /* 有渲染样本时推导出的理论最大生成帧率。 */ + std::optional 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; /* 由策略互斥串行发布的帧策略运行统计。 */ }; } diff --git a/kernel/kernel/include/function/frame_policy/Frame_Policy.ipp b/kernel/kernel/include/function/frame_policy/Frame_Policy.ipp index f352ef6..70ef2e6 100644 --- a/kernel/kernel/include/function/frame_policy/Frame_Policy.ipp +++ b/kernel/kernel/include/function/frame_policy/Frame_Policy.ipp @@ -1,88 +1,40 @@ #pragma once namespace aethera { -template -Frame_Policy::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 -bool Frame_Policy::Private::prepare_statistics( - const Prop& properties, - Export_Struct& 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& published_state); + void record_timer_tick( + bool dropped, + Export_Struct& published_state); + void record_render( + std::chrono::nanoseconds duration, + Export_Struct& published_state); + void record_send( + std::chrono::nanoseconds send_duration, + std::chrono::nanoseconds end_to_end_duration, + Export_Struct& published_state); + [[nodiscard]] double effective_frames_per_second() const noexcept; -template -void Frame_Policy::Private::record_timer_tick( - bool dropped, - Export_Struct& published_state) { - if (!statistics) { - return; - } - ++timer_ticks; - if (dropped) { - ++dropped_timer_ticks; - } - publish(published_state); -} +private: + void publish(Export_Struct& published_state); -template -void Frame_Policy::Private::record_render( - std::chrono::nanoseconds duration, - Export_Struct& published_state) { - if (!statistics) { - return; - } - statistics->render.add(static_cast(duration.count())); - publish(published_state); -} - -template -void Frame_Policy::Private::record_send( - std::chrono::nanoseconds send_duration, - std::chrono::nanoseconds end_to_end_duration, - Export_Struct& published_state) { - if (!statistics) { - return; - } - statistics->send.add(static_cast(send_duration.count())); - statistics->end_to_end.add( - static_cast(end_to_end_duration.count())); - ++completed_frames; - publish(published_state); -} - -template -void Frame_Policy::Private::publish( - Export_Struct& 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 statistics; /* 存在即表示本次运行需要耗时窗口。 */ + std::optional 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{}; /* 本次运行累计的完成帧数。 */ +}; } diff --git a/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.cpp b/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.cpp index dda5e20..4a8e3e2 100644 --- a/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.cpp +++ b/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.cpp @@ -35,7 +35,7 @@ std::expected 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::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(duration)); +} } Throttled_Latest_only::Throttled_Latest_only( - proxy timer_service, + proxy timer_service, proxy scene, proxy 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(); 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()); } Throttled_Latest_only::Stop_Result @@ -79,7 +86,7 @@ Throttled_Latest_only::stop(Stop_Completion completion) { } Throttled_Latest_only::Private::Private( - proxy timer_service, + proxy timer_service, proxy scene, proxy 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& published_state, - double frames_per_second) { - const auto interval = frame_interval(frames_per_second); + Export_Struct& 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( - 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( - slot.send_end - slot.send_start), - std::chrono::duration_cast( - 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; } diff --git a/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.hpp b/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.hpp index b880127..d9c6ad1 100644 --- a/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.hpp +++ b/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.hpp @@ -5,11 +5,11 @@ #include namespace aethera { /* - * 固定帧率、最多三帧在途的 latest-only 策略。Scene 创建三个具体帧;策略轮转复用, + * 自适应限速、最多三帧在途的 latest-only 策略。Scene 创建三个具体帧;策略轮转复用, * 三槽均忙时丢弃本次 tick;stop completion 前排空 Scene/Sink 借用并销毁全部帧。 */ struct Throttled_Latest_only : -Def> { +Def { enum struct Start_Result : std::uint8_t { started, already_running, @@ -17,7 +17,7 @@ Def> { 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> { using Stop_Completion = std::function; struct Prop : Prev {}; struct Private; - Throttled_Latest_only(proxy timer_service, + Throttled_Latest_only(proxy timer_service, proxy scene, proxy sink); ~Throttled_Latest_only() noexcept; - Start_Result start(double frames_per_second); + Start_Result start(); Stop_Result stop(Stop_Completion completion); }; } diff --git a/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.ipp b/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.ipp index ccb7ba9..7a2d4e1 100644 --- a/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.ipp +++ b/kernel/kernel/include/function/frame_policy/Throttled_Latest_Only.ipp @@ -29,14 +29,13 @@ struct Throttled_Latest_only::Private : Prev_Private { static constexpr std::size_t Frame_Count{3}; - Private(proxy timer_service, + Private(proxy timer_service, proxy scene, proxy sink); Start_Result start( const Prop& properties, - Export_Struct& published_state, - double frames_per_second); + Export_Struct& 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& published_state); + void reschedule_next_frame(); void rendered( std::size_t slot_index, Export_Struct& published_state, @@ -61,7 +61,7 @@ private: void abandon_send(std::size_t slot_index); void finish_stop_if_ready(); - proxy timer_service; /* 周期调度和异步取消的必填业务能力所有权。 */ + proxy timer_service; /* 动态重设期限和异步取消的必填业务能力所有权。 */ proxy scene; /* 帧池创建及渲染借用的必填业务能力所有权。 */ proxy sink; /* 已渲染帧媒体分派的必填业务能力所有权。 */ std::array frames; /* 固定三个槽位的轮转帧池。 */ diff --git a/kernel/kernel/include/model/Model.hpp b/kernel/kernel/include/model/Model.hpp index 3c6388e..7930baf 100644 --- a/kernel/kernel/include/model/Model.hpp +++ b/kernel/kernel/include/model/Model.hpp @@ -1,120 +1,94 @@ #pragma once - #include "../concurrent/Concurrent_Storage_with_dirty.hpp" #include "../concurrent/storage/Tagged_Storage.hpp" - #include #include - namespace aethera { namespace detail { -template -struct Model_Type_List {}; - -template -struct Append_Model_Registrations; -template -struct Model_Tag_Registered; -template -struct Previous_Model_Value; - +template struct Model_Type_List {}; +template struct Append_Model_Registrations; +template struct Model_Tag_Registered; +template struct Previous_Model_Value; struct Empty_Prop {}; struct Empty_State {}; } - /* Tag 负责选择各定义层的 Prop 类型;同一个 Tag 在注册表中只允许出现一次。 */ struct Prop_Tag { using Root_Type = detail::Empty_Prop; - template - using Value = typename Layer::Prop; + template using Value = typename Layer::Prop; }; - /* Tag 负责选择各定义层的 State 类型。 */ struct State_Tag { using Root_Type = detail::Empty_State; - template - using Value = typename Layer::State; + template using Value = typename Layer::State; }; - /* 把一个业务 Tag 注册到接受单个值类型的 Concurrent Storage 模板。 */ -template typename Concurrent_Storage> -struct Storage_Registration { +template typename Concurrent_Storage> struct Storage_Registration { using Tag_Type = Tag; - template - using Storage = Concurrent_Storage; + template using Storage = Concurrent_Storage; }; - -template struct Root { using Storage_Registrations = detail::Model_Type_List<>; - template - using Prev = typename Tag::Root_Type; - + template using Prev = typename Tag::Root_Type; struct Private {}; struct Builder {}; }; - /* * 只扩展类型与存储注册链,不创建运行时 d;供多个业务定义层共同组成最终 Def。 */ -template -struct Model_Layer : Base { +template struct Model_Layer : Base { using Storage_Registrations = typename detail::Append_Model_Registrations< typename Base::Storage_Registrations, Registrations...>::Type; - template - using Prev = typename detail::Previous_Model_Value< + template 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 -struct Def : Model_Layer { - using Layer = Model_Layer; +template struct Def : Model_Layer { + using Layer = Model_Layer; using Storage_Registrations = typename Layer::Storage_Registrations; using Prev_Private = typename Base::Private; - struct Private; struct Builder : Base::Builder { - template - explicit Builder(Args&&... args); + template explicit Builder(Args&&... args); std::unique_ptr build(); template < template typename Concurrent_Storage, typename Tag, - typename... Args> - requires requires( + typename... Args> requires requires( Concurrent_Storage>& storage, Args&&... args) { storage.write(std::forward(args)...); } Builder& set(Args&&... args); - std::unique_ptr ret; /* 构建期间唯一拥有尚未发布的对象。 */ }; - template < template typename Concurrent_Storage, typename Tag, - typename... Args> - requires requires( + typename... Args> requires requires( Concurrent_Storage>& storage, Args&&... args) { storage.write(std::forward(args)...); } Self& write(Args&&... args); - template - explicit Def(Args&&... args); - + template < + template typename Concurrent_Storage, + typename Tag, + typename... Args> requires requires( + const Concurrent_Storage>& storage, + Args&&... args) { + storage.read(std::forward(args)...); + } + const Self& read(Args&&... args) const; + template explicit Def(Args&&... args); std::unique_ptr d; /* 唯一拥有最终 Private 和完整 Tagged Storage 集。 */ }; } - #include "Model.ipp" diff --git a/kernel/kernel/include/model/Model.ipp b/kernel/kernel/include/model/Model.ipp index fa5bb1a..19d2a3d 100644 --- a/kernel/kernel/include/model/Model.ipp +++ b/kernel/kernel/include/model/Model.ipp @@ -162,4 +162,25 @@ Self& Def::write(Args&&... args) { storage.write(std::forward(args)...); return static_cast(*this); } + +template +template < + template typename Concurrent_Storage, + typename Tag, + typename... Args> +requires requires( + const Concurrent_Storage>& storage, + Args&&... args) { + storage.read(std::forward(args)...); +} +const Self& Def::read(Args&&... args) const { + const auto& storage = d->template get(); + using Value = typename Tag::template Value; + static_assert(std::same_as< + std::remove_cvref_t, + Concurrent_Storage>, + "the concurrent storage and tag must name one registration"); + storage.read(std::forward(args)...); + return static_cast(*this); +} } diff --git a/kernel/kernel/include/model/design.md b/kernel/kernel/include/model/design.md index f545dc2..3222376 100644 --- a/kernel/kernel/include/model/design.md +++ b/kernel/kernel/include/model/design.md @@ -7,6 +7,6 @@ `Model_Layer` 通过 `Storage_Registration` 注册一条业务值链。每个定义层用 `Prev` 取得前一层值类型并继承;最终 `Def::Private` 根据完整注册表只创建一次 `Tagged_Storage_Set`。同一 Tag 重复注册属于编译期错误。 -Builder 使用 `set(...)`,最终模型使用 `write(...)`。两个模板参数必须命中同一注册项,参数能力直接由对应并发存储的 `write(...)` 重载约束:Struct 可以整对象或按成员写,List 只接受列表批次写入。Builder 对 Struct 同时建立内部读面和外部写面且不产生 dirty;其他存储保留自身写入/消费语义。 +Builder 使用 `set(...)`,最终模型使用 `write(...)` 和 `read(...)`。两个模板参数必须命中同一注册项,参数能力直接由对应并发存储的 `write/read(...)` 重载约束:Struct 可以整对象或按成员读写,List 保留自身批次语义。Builder 对 Struct 同时建立内部读面和外部写面且不产生 dirty;其他存储保留自身写入/消费语义。 属性 binder 只在对应 Tag 存储的 `internal.advance()` 之后、同一内部线程阶段查询。外部写入、`advance()` 和 binder 查询按阶段串行且不重叠,因此 dirty revision 与属性表不使用锁或原子。 diff --git a/kernel/kernel/test/function/frame_policy/Throttled_Latest_Only_Tests.cpp b/kernel/kernel/test/function/frame_policy/Throttled_Latest_Only_Tests.cpp index 2bb6427..a3e0144 100644 --- a/kernel/kernel/test/function/frame_policy/Throttled_Latest_Only_Tests.cpp +++ b/kernel/kernel/test/function/frame_policy/Throttled_Latest_Only_Tests.cpp @@ -3,11 +3,13 @@ #include +#include #include #include #include #include #include +#include #include #include #include @@ -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 control; /* 服务调用记录的共享所有权。 */ }; @@ -151,17 +169,29 @@ struct Test_Sink { std::unique_ptr make_policy( const std::shared_ptr& control, + std::optional 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(control); auto sink = pro::make_proxy(control); aethera::Throttled_Latest_only::Builder builder( std::move(timer_service), std::move(scene), std::move(sink)); builder + .set( + &aethera::Throttled_Latest_only::Prop::user_frames_per_second, + user_frames_per_second) + .set( + &aethera::Throttled_Latest_only::Prop::render_rate_limit_enabled, + render_rate_limit_enabled) + .set( + &aethera::Throttled_Latest_only::Prop::send_rate_limit_enabled, + send_rate_limit_enabled) .set( &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(); - 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( + [](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(); - 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( + [](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(); + 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(); + 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( + [](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(); + 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( + [](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(); 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( + [](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::proxy{}, aethera::proxy{}, aethera::proxy{}); 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(); - 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::denorm_min()); EXPECT_EQ( - policy->start(std::numeric_limits::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(); 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); diff --git a/kernel/kernel/test/model/Model_Tests.cpp b/kernel/kernel/test/model/Model_Tests.cpp index 6d3217f..8ba468e 100644 --- a/kernel/kernel/test/model/Model_Tests.cpp +++ b/kernel/kernel/test/model/Model_Tests.cpp @@ -15,33 +15,21 @@ struct Number_List_Tag { using Value = typename Layer::Numbers; }; -template struct Test_Model_Base : aethera::Model_Layer< - Self, - aethera::Root, + aethera::Root, aethera::Storage_Registration< aethera::Prop_Tag, aethera::Import_Struct_With_Dirty>, aethera::Storage_Registration> { - using Layer = aethera::Model_Layer< - Self, - aethera::Root, - aethera::Storage_Registration< - aethera::Prop_Tag, - aethera::Import_Struct_With_Dirty>, - aethera::Storage_Registration>; - using Prev_Private = typename Layer::Prev_Private; - using Prev_Builder = typename Layer::Prev_Builder; - - struct Prop : Layer::template Prev { + struct Prop : Prev { int base_number{}; }; - struct Numbers : Layer::template Prev {}; + struct Numbers : Prev {}; struct Private : Prev_Private {}; struct Builder : Prev_Builder {}; }; -struct Test_Model : aethera::Def> { +struct Test_Model : aethera::Def { struct Prop : Prev { int number{}; int other{}; @@ -52,10 +40,10 @@ struct Test_Model : aethera::Def> { static_assert(std::derived_from< Test_Model::Prop, - Test_Model_Base::Prop>); + Test_Model_Base::Prop>); static_assert(std::derived_from< Test_Model::Numbers, - Test_Model_Base::Numbers>); + Test_Model_Base::Numbers>); TEST(Model, Registered_Tags_Build_Independent_Chained_Storages) { Test_Model::Builder builder;