This commit is contained in:
2026-09-01 08:51:01 +08:00
parent c973c7c0a9
commit aedef4f60b
12 changed files with 489 additions and 532 deletions
@@ -0,0 +1,361 @@
#include "Throttled_Latest_Only.hpp"
#include <algorithm>
#include <cmath>
#include <exception>
#include <expected>
#include <limits>
#include <utility>
namespace aethera {
namespace {
template<typename Function>
struct Scope_Exit {
explicit Scope_Exit(Function function) :
function(std::move(function)) {}
~Scope_Exit() noexcept {
if (active) {
function();
}
}
Scope_Exit(const Scope_Exit&) = delete;
Scope_Exit& operator=(const Scope_Exit&) = delete;
void release() noexcept {
active = false;
}
Function function; /* 离开当前作用域时执行的异常安全清理。 */
bool active{true}; /* release 后不再执行清理。 */
};
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);
}
constexpr long double Nanoseconds_Per_Second = 1'000'000'000.0L;
const long double interval =
Nanoseconds_Per_Second /
static_cast<long double>(frames_per_second);
constexpr auto Maximum_Interval = static_cast<long double>(
std::numeric_limits<std::chrono::nanoseconds::rep>::max());
if (!std::isfinite(interval) || interval > Maximum_Interval) {
return std::unexpected(Start_Result::interval_out_of_range);
}
return std::chrono::nanoseconds{
static_cast<std::chrono::nanoseconds::rep>(
std::max(1.0L, std::ceil(interval)))};
}
}
Throttled_Latest_only::Throttled_Latest_only(
proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink) :
Def(std::move(timer_service), std::move(scene), std::move(sink)) {}
Throttled_Latest_only::~Throttled_Latest_only() noexcept {
if (!d->destructible()) {
std::terminate();
}
}
Throttled_Latest_only::Start_Result
Throttled_Latest_only::start(double frames_per_second) {
return d->start(frames_per_second);
}
Throttled_Latest_only::Stop_Result
Throttled_Latest_only::stop(Stop_Completion completion) {
return d->stop(std::move(completion));
}
Throttled_Latest_only::Private::Private(
proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink) :
timer_service(std::move(timer_service)),
scene(std::move(scene)),
sink(std::move(sink)) {}
Throttled_Latest_only::Start_Result
Throttled_Latest_only::Private::start(double frames_per_second) {
const auto interval = frame_interval(frames_per_second);
if (!interval) {
return interval.error();
}
const auto prepared = prepare_start();
if (prepared != Start_Result::started) {
return prepared;
}
Timer_Id scheduled_timer_id{};
try {
scheduled_timer_id = timer_service->schedule_every(
*interval,
[this] {
frame_due();
});
}
catch (...) {
start_failed();
throw;
}
timer_started(scheduled_timer_id);
return Start_Result::started;
}
Throttled_Latest_only::Stop_Result
Throttled_Latest_only::Private::stop(Stop_Completion completion) {
if (!completion) {
return Stop_Result::completion_missing;
}
Timer_Id cancelled_timer_id{};
{
std::lock_guard lock(mutex);
switch (phase) {
case Phase::stopped:
return Stop_Result::already_stopped;
case Phase::starting:
return Stop_Result::start_in_progress;
case Phase::stopping:
return Stop_Result::already_stopping;
case Phase::running:
break;
}
if (!timer_id) {
std::terminate();
}
cancelled_timer_id = *timer_id;
phase = Phase::stopping;
stop_completion = std::move(completion);
timer_cancel_confirmed = false;
}
try {
timer_service->cancel(
cancelled_timer_id,
[this] {
timer_cancelled();
});
}
catch (...) {
cancel_failed();
throw;
}
return Stop_Result::stopping;
}
Throttled_Latest_only::Start_Result
Throttled_Latest_only::Private::prepare_start() {
{
std::lock_guard lock(mutex);
switch (phase) {
case Phase::running:
return Start_Result::already_running;
case Phase::starting:
return Start_Result::start_in_progress;
case Phase::stopping:
return Start_Result::stop_in_progress;
case Phase::stopped:
break;
}
if (!timer_service || !scene || !sink) {
return Start_Result::dependency_unavailable;
}
phase = Phase::starting;
}
Scope_Exit rollback([this] {
std::lock_guard lock(mutex);
if (phase == Phase::starting) {
phase = Phase::stopped;
}
});
auto created_frame = scene->create_frame();
if (!created_frame) {
return Start_Result::frame_unavailable;
}
{
std::lock_guard lock(mutex);
if (phase != Phase::starting || frame) {
std::terminate();
}
frame = std::move(created_frame);
frame_phase = Frame_Phase::idle;
timer_cancel_confirmed = false;
}
rollback.release();
return Start_Result::started;
}
void Throttled_Latest_only::Private::timer_started(Timer_Id id) {
std::lock_guard lock(mutex);
if (id == 0 ||
phase != Phase::starting ||
!frame ||
timer_id.has_value()) {
std::terminate();
}
timer_id = id;
phase = Phase::running;
}
void Throttled_Latest_only::Private::start_failed() {
proxy<FP_Frame> retired_frame;
{
std::lock_guard lock(mutex);
if (phase != Phase::starting || timer_id) {
std::terminate();
}
retired_frame = std::move(frame);
frame_phase = Frame_Phase::idle;
phase = Phase::stopped;
}
retired_frame.reset();
}
void Throttled_Latest_only::Private::cancel_failed() {
std::lock_guard lock(mutex);
if (phase != Phase::stopping || timer_cancel_confirmed) {
std::terminate();
}
stop_completion = {};
phase = Phase::running;
}
void Throttled_Latest_only::Private::timer_cancelled() {
{
std::lock_guard lock(mutex);
if (phase != Phase::stopping || timer_cancel_confirmed) {
std::terminate();
}
timer_id.reset();
timer_cancel_confirmed = true;
}
finish_stop_if_ready();
}
void Throttled_Latest_only::Private::frame_due() {
{
std::lock_guard lock(mutex);
if (phase != Phase::running || frame_phase != Frame_Phase::idle) {
return;
}
frame_phase = Frame_Phase::rendering;
}
try {
scene->render(
frame,
[this](proxy<FP_Frame>& completed_frame) {
rendered(completed_frame);
});
}
catch (...) {
abandon_render();
throw;
}
}
void Throttled_Latest_only::Private::rendered(
proxy<FP_Frame>& completed_frame) {
{
std::lock_guard lock(mutex);
if (std::addressof(completed_frame) != std::addressof(frame) ||
frame_phase != Frame_Phase::rendering) {
std::terminate();
}
frame_phase = Frame_Phase::sending;
}
try {
sink->send(
frame,
[this](proxy<FP_Frame>& sent_frame) {
sent(sent_frame);
});
}
catch (...) {
abandon_send();
throw;
}
}
void Throttled_Latest_only::Private::sent(
proxy<FP_Frame>& completed_frame) {
{
std::lock_guard lock(mutex);
if (std::addressof(completed_frame) != std::addressof(frame) ||
frame_phase != Frame_Phase::sending) {
std::terminate();
}
frame_phase = Frame_Phase::idle;
}
finish_stop_if_ready();
}
void Throttled_Latest_only::Private::abandon_render() {
{
std::lock_guard lock(mutex);
if (frame_phase != Frame_Phase::rendering) {
return;
}
frame_phase = Frame_Phase::idle;
}
finish_stop_if_ready();
}
void Throttled_Latest_only::Private::abandon_send() {
{
std::lock_guard lock(mutex);
if (frame_phase != Frame_Phase::sending) {
return;
}
frame_phase = Frame_Phase::idle;
}
finish_stop_if_ready();
}
void Throttled_Latest_only::Private::finish_stop_if_ready() {
proxy<FP_Frame> retired_frame;
Stop_Completion completion;
{
std::lock_guard lock(mutex);
if (phase != Phase::stopping ||
!timer_cancel_confirmed ||
frame_phase != Frame_Phase::idle) {
return;
}
retired_frame = std::move(frame);
completion = std::move(stop_completion);
timer_cancel_confirmed = false;
phase = Phase::stopped;
}
retired_frame.reset();
try {
completion();
}
catch (...) {
std::terminate();
}
}
bool Throttled_Latest_only::Private::destructible() const noexcept {
std::lock_guard lock(mutex);
return phase == Phase::stopped &&
frame_phase == Frame_Phase::idle &&
!frame &&
!timer_id &&
!stop_completion;
}
}
@@ -0,0 +1,41 @@
#pragma once
#include "function/frame_policy/global.hpp"
#include "model/Model.hpp"
#include <cstdint>
#include <functional>
namespace aethera {
/*
* 固定帧率、最多一帧在途的策略。Scene 创建具体帧;策略持有并重复使用,
* stop completion 执行前会排空 Scene/Sink 借用并销毁该帧。
*/
struct Throttled_Latest_only :
Def<Throttled_Latest_only, Root<Throttled_Latest_only>> {
enum struct Start_Result : std::uint8_t {
started,
already_running,
start_in_progress,
stop_in_progress,
dependency_unavailable,
frame_unavailable,
invalid_frames_per_second,
interval_out_of_range
};
enum struct Stop_Result : std::uint8_t {
stopping,
already_stopped,
already_stopping,
start_in_progress,
completion_missing
};
using Stop_Completion = std::function<void()>;
struct Prop : Prev_Prop {};
struct Private;
Throttled_Latest_only(proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink);
~Throttled_Latest_only() noexcept;
Start_Result start(double frames_per_second);
Stop_Result stop(Stop_Completion completion);
};
}
#include "Throttled_Latest_Only.ipp"
@@ -0,0 +1,52 @@
#pragma once
#include <mutex>
#include <optional>
namespace aethera {
struct Throttled_Latest_only::Private : Prev_Private {
enum struct Phase : std::uint8_t {
stopped,
starting,
running,
stopping
};
enum struct Frame_Phase : std::uint8_t {
idle,
rendering,
sending
};
Private(proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink);
Start_Result start(double frames_per_second);
Stop_Result stop(Stop_Completion completion);
[[nodiscard]] bool destructible() const noexcept;
private:
Start_Result prepare_start();
void timer_started(Timer_Id id);
void start_failed();
void cancel_failed();
void timer_cancelled();
void frame_due();
void rendered(proxy<FP_Frame>& completed_frame);
void sent(proxy<FP_Frame>& completed_frame);
void abandon_render();
void abandon_send();
void finish_stop_if_ready();
proxy<Periodic_Timer_Service> timer_service; /* 周期调度和异步取消的必填业务能力所有权。 */
proxy<FP_Scene> scene; /* 帧创建及渲染借用的必填业务能力所有权。 */
proxy<FP_Sink> sink; /* 已渲染帧媒体分派的必填业务能力所有权。 */
proxy<FP_Frame> frame; /* 策略创建、复用并在停止完成前销毁的唯一帧。 */
Stop_Completion stop_completion; /* 本次异步停止的唯一完成回调。 */
std::optional<Timer_Id> timer_id; /* running/stopping 阶段的周期计时器标识。 */
mutable std::mutex mutex; /* 保护生命周期和跨线程帧借用阶段。 */
Phase phase{Phase::stopped}; /* start/stop 生命周期的唯一权威状态。 */
Frame_Phase frame_phase{Frame_Phase::idle}; /* 唯一帧当前借用方的权威状态。 */
bool timer_cancel_confirmed{}; /* stopping 阶段的异步取消确认。 */
};
}
@@ -1,354 +0,0 @@
#include "Throttled_Latest_Only_State.ipp"
#include <algorithm>
#include <cmath>
#include <exception>
#include <expected>
#include <limits>
#include <utility>
namespace aethera::detail {
namespace {
template<typename Function>
struct Scope_Exit {
explicit Scope_Exit(Function function) :
function(std::move(function)) {}
~Scope_Exit() noexcept {
if (active) {
function();
}
}
Scope_Exit(const Scope_Exit&) = delete;
Scope_Exit& operator=(const Scope_Exit&) = delete;
void release() noexcept {
active = false;
}
Function function; /* 离开当前作用域时执行的异常安全清理。 */
bool active{true}; /* release 后不再执行清理。 */
};
std::expected<
std::chrono::nanoseconds,
Start_Throttled_Latest_Only_Result>
frame_interval(double frames_per_second) noexcept {
if (!std::isfinite(frames_per_second) || frames_per_second <= 0.0) {
return std::unexpected(
Start_Throttled_Latest_Only_Result::invalid_frames_per_second);
}
constexpr long double Nanoseconds_Per_Second = 1'000'000'000.0L;
const long double interval =
Nanoseconds_Per_Second /
static_cast<long double>(frames_per_second);
constexpr auto Maximum_Interval = static_cast<long double>(
std::numeric_limits<std::chrono::nanoseconds::rep>::max());
if (!std::isfinite(interval) || interval > Maximum_Interval) {
return std::unexpected(
Start_Throttled_Latest_Only_Result::interval_out_of_range);
}
return std::chrono::nanoseconds{
static_cast<std::chrono::nanoseconds::rep>(
std::max(1.0L, std::ceil(interval)))};
}
}
Throttled_Latest_Only_State::Throttled_Latest_Only_State(
proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink) :
d(std::make_unique<Private>()) {
d->timer_service = std::move(timer_service);
d->scene = std::move(scene);
d->sink = std::move(sink);
}
Throttled_Latest_Only_State::~Throttled_Latest_Only_State() = default;
Start_Throttled_Latest_Only_Result
Throttled_Latest_Only_State::start(double frames_per_second) {
const auto interval = frame_interval(frames_per_second);
if (!interval) {
return interval.error();
}
const auto prepared = prepare_start();
if (prepared != Start_Throttled_Latest_Only_Result::started) {
return prepared;
}
Timer_Id timer_id{};
try {
auto self = shared_from_this();
timer_id = d->timer_service->schedule_every(
*interval,
[self = std::move(self)] {
self->frame_due();
});
}
catch (...) {
start_failed();
throw;
}
timer_started(timer_id);
return Start_Throttled_Latest_Only_Result::started;
}
Stop_Throttled_Latest_Only_Result
Throttled_Latest_Only_State::stop(
Throttled_Latest_Only_Stop_Completion completion) {
if (!completion) {
return Stop_Throttled_Latest_Only_Result::completion_missing;
}
Timer_Id timer_id{};
{
std::lock_guard lock(d->mutex);
switch (d->phase) {
case Private::Phase::stopped:
return Stop_Throttled_Latest_Only_Result::already_stopped;
case Private::Phase::starting:
return Stop_Throttled_Latest_Only_Result::start_in_progress;
case Private::Phase::stopping:
return Stop_Throttled_Latest_Only_Result::already_stopping;
case Private::Phase::running:
break;
}
if (!d->timer_id) {
std::terminate();
}
timer_id = *d->timer_id;
d->phase = Private::Phase::stopping;
d->stop_completion = std::move(completion);
d->timer_cancelled = false;
}
try {
auto self = shared_from_this();
d->timer_service->cancel(
timer_id,
[self = std::move(self)] {
self->timer_cancelled();
});
}
catch (...) {
cancel_failed();
throw;
}
return Stop_Throttled_Latest_Only_Result::stopping;
}
Start_Throttled_Latest_Only_Result
Throttled_Latest_Only_State::prepare_start() {
{
std::lock_guard lock(d->mutex);
switch (d->phase) {
case Private::Phase::running:
return Start_Throttled_Latest_Only_Result::already_running;
case Private::Phase::starting:
return Start_Throttled_Latest_Only_Result::start_in_progress;
case Private::Phase::stopping:
return Start_Throttled_Latest_Only_Result::stop_in_progress;
case Private::Phase::stopped:
break;
}
if (!d->timer_service || !d->scene || !d->sink) {
return Start_Throttled_Latest_Only_Result::dependency_unavailable;
}
d->phase = Private::Phase::starting;
}
Scope_Exit rollback([this] {
std::lock_guard lock(d->mutex);
if (d->phase == Private::Phase::starting) {
d->phase = Private::Phase::stopped;
}
});
auto frame = d->scene->create_frame();
if (!frame) {
return Start_Throttled_Latest_Only_Result::frame_unavailable;
}
{
std::lock_guard lock(d->mutex);
if (d->phase != Private::Phase::starting || d->frame) {
std::terminate();
}
d->frame = std::move(frame);
d->frame_phase = Private::Frame_Phase::idle;
d->timer_cancelled = false;
}
rollback.release();
return Start_Throttled_Latest_Only_Result::started;
}
void Throttled_Latest_Only_State::timer_started(Timer_Id id) {
std::lock_guard lock(d->mutex);
if (id == 0 ||
d->phase != Private::Phase::starting ||
!d->frame ||
d->timer_id.has_value()) {
std::terminate();
}
d->timer_id = id;
d->phase = Private::Phase::running;
}
void Throttled_Latest_Only_State::start_failed() {
proxy<FP_Frame> retired_frame;
{
std::lock_guard lock(d->mutex);
if (d->phase != Private::Phase::starting || d->timer_id) {
std::terminate();
}
retired_frame = std::move(d->frame);
d->frame_phase = Private::Frame_Phase::idle;
d->phase = Private::Phase::stopped;
}
retired_frame.reset();
}
void Throttled_Latest_Only_State::cancel_failed() {
std::lock_guard lock(d->mutex);
if (d->phase != Private::Phase::stopping || d->timer_cancelled) {
std::terminate();
}
d->stop_completion = {};
d->phase = Private::Phase::running;
}
void Throttled_Latest_Only_State::timer_cancelled() {
{
std::lock_guard lock(d->mutex);
if (d->phase != Private::Phase::stopping || d->timer_cancelled) {
std::terminate();
}
d->timer_id.reset();
d->timer_cancelled = true;
}
finish_stop_if_ready();
}
void Throttled_Latest_Only_State::frame_due() {
{
std::lock_guard lock(d->mutex);
if (d->phase != Private::Phase::running ||
d->frame_phase != Private::Frame_Phase::idle) {
return;
}
d->frame_phase = Private::Frame_Phase::rendering;
}
auto self = shared_from_this();
try {
d->scene->render(
d->frame,
[self = std::move(self)](proxy<FP_Frame>& frame) {
self->rendered(frame);
});
}
catch (...) {
abandon_render();
throw;
}
}
void Throttled_Latest_Only_State::rendered(
proxy<FP_Frame>& completed_frame) {
{
std::lock_guard lock(d->mutex);
if (std::addressof(completed_frame) !=
std::addressof(d->frame) ||
d->frame_phase != Private::Frame_Phase::rendering) {
std::terminate();
}
d->frame_phase = Private::Frame_Phase::sending;
}
auto self = shared_from_this();
try {
d->sink->send(
d->frame,
[self = std::move(self)](proxy<FP_Frame>& frame) {
self->sent(frame);
});
}
catch (...) {
abandon_send();
throw;
}
}
void Throttled_Latest_Only_State::sent(
proxy<FP_Frame>& completed_frame) {
{
std::lock_guard lock(d->mutex);
if (std::addressof(completed_frame) !=
std::addressof(d->frame) ||
d->frame_phase != Private::Frame_Phase::sending) {
std::terminate();
}
d->frame_phase = Private::Frame_Phase::idle;
}
finish_stop_if_ready();
}
void Throttled_Latest_Only_State::abandon_render() {
{
std::lock_guard lock(d->mutex);
if (d->frame_phase != Private::Frame_Phase::rendering) {
return;
}
d->frame_phase = Private::Frame_Phase::idle;
}
finish_stop_if_ready();
}
void Throttled_Latest_Only_State::abandon_send() {
{
std::lock_guard lock(d->mutex);
if (d->frame_phase != Private::Frame_Phase::sending) {
return;
}
d->frame_phase = Private::Frame_Phase::idle;
}
finish_stop_if_ready();
}
void Throttled_Latest_Only_State::finish_stop_if_ready() {
proxy<FP_Frame> retired_frame;
Throttled_Latest_Only_Stop_Completion completion;
{
std::lock_guard lock(d->mutex);
if (d->phase != Private::Phase::stopping ||
!d->timer_cancelled ||
d->frame_phase != Private::Frame_Phase::idle) {
return;
}
retired_frame = std::move(d->frame);
completion = std::move(d->stop_completion);
d->timer_cancelled = false;
d->phase = Private::Phase::stopped;
}
retired_frame.reset();
try {
completion();
}
catch (...) {
std::terminate();
}
}
bool Throttled_Latest_Only_State::destructible() const noexcept {
std::lock_guard lock(d->mutex);
return d->phase == Private::Phase::stopped &&
d->frame_phase == Private::Frame_Phase::idle &&
!d->frame &&
!d->timer_id &&
!d->stop_completion;
}
}
@@ -1,36 +0,0 @@
#pragma once
#include "function/frame_policy/global.hpp"
#include <memory>
namespace aethera::detail {
struct Throttled_Latest_Only_State :
Non_Copyable,
std::enable_shared_from_this<Throttled_Latest_Only_State> {
Throttled_Latest_Only_State(proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink);
~Throttled_Latest_Only_State();
Start_Throttled_Latest_Only_Result start(double frames_per_second);
Stop_Throttled_Latest_Only_Result stop(
Throttled_Latest_Only_Stop_Completion completion);
[[nodiscard]] bool destructible() const noexcept;
private:
Start_Throttled_Latest_Only_Result prepare_start();
void timer_started(Timer_Id id);
void start_failed();
void cancel_failed();
void timer_cancelled();
void frame_due();
void rendered(proxy<FP_Frame>& completed_frame);
void sent(proxy<FP_Frame>& completed_frame);
void abandon_render();
void abandon_send();
void finish_stop_if_ready();
struct Private;
std::unique_ptr<Private> d; /* 策略异步生命周期和唯一帧状态的所有权。 */
};
}
@@ -1,34 +0,0 @@
#pragma once
#include "Throttled_Latest_Only_State.hpp"
#include <mutex>
#include <optional>
namespace aethera::detail {
struct Throttled_Latest_Only_State::Private {
enum struct Phase : std::uint8_t {
stopped,
starting,
running,
stopping
};
enum struct Frame_Phase : std::uint8_t {
idle,
rendering,
sending
};
proxy<Periodic_Timer_Service> timer_service; /* 周期调度和异步取消的业务能力所有权。 */
proxy<FP_Scene> scene; /* 帧类型和渲染借用的业务实现所有权。 */
proxy<FP_Sink> sink; /* 当前帧媒体分派的业务实现所有权。 */
proxy<FP_Frame> frame; /* 策略创建、复用并在停止完成前销毁的唯一帧。 */
Throttled_Latest_Only_Stop_Completion stop_completion; /* 本次异步停止的唯一完成回调。 */
std::optional<Timer_Id> timer_id; /* running/stopping 阶段的周期计时器标识。 */
mutable std::mutex mutex; /* 保护生命周期和跨线程帧借用阶段。 */
Phase phase{Phase::stopped}; /* start/stop 生命周期的唯一权威状态。 */
Frame_Phase frame_phase{Frame_Phase::idle}; /* 唯一帧当前借用方的权威状态。 */
bool timer_cancelled{}; /* stopping 阶段的异步取消确认。 */
};
}
@@ -1,41 +1,4 @@
#include "global.ipp"
#include <exception>
#include <utility>
namespace aethera {
Throttled_Latest_only::Private::Private(
proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink) :
state(std::make_shared<detail::Throttled_Latest_Only_State>(
std::move(timer_service),
std::move(scene),
std::move(sink))) {}
Throttled_Latest_only::Throttled_Latest_only(
proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink) :
d(std::make_unique<Private>(
std::move(timer_service),
std::move(scene),
std::move(sink))) {}
Throttled_Latest_only::~Throttled_Latest_only() noexcept {
if (!d->state->destructible()) {
std::terminate();
}
}
Start_Throttled_Latest_Only_Result
Throttled_Latest_only::start(double frames_per_second) {
return d->state->start(frames_per_second);
}
Stop_Throttled_Latest_Only_Result
Throttled_Latest_only::stop(
Throttled_Latest_Only_Stop_Completion completion) {
return d->state->stop(std::move(completion));
}
}
namespace aethera {}
@@ -23,40 +23,4 @@ PRO_DEF_MEM_DISPATCH(FP_send, send);
struct FP_Sink : facade_builder
::add_convention<FP_send, void(proxy<FP_Frame>&, FP_Frame_Completion)>
::build {};
enum struct Start_Throttled_Latest_Only_Result : std::uint8_t {
started,
already_running,
start_in_progress,
stop_in_progress,
dependency_unavailable,
frame_unavailable,
invalid_frames_per_second,
interval_out_of_range
};
enum struct Stop_Throttled_Latest_Only_Result : std::uint8_t {
stopping,
already_stopped,
already_stopping,
start_in_progress,
completion_missing
};
using Throttled_Latest_Only_Stop_Completion =
std::function<void()>;
/*
* 固定帧率、最多一帧在途的策略。Scene 创建具体帧;策略持有并重复使用,
* stop completion 执行前会排空 Scene/Sink 借用并销毁该帧。
*/
struct Throttled_Latest_only : Immovable {
Throttled_Latest_only(proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink);
~Throttled_Latest_only() noexcept;
Start_Throttled_Latest_Only_Result start(
double frames_per_second);
Stop_Throttled_Latest_Only_Result stop(
Throttled_Latest_Only_Stop_Completion completion);
private:
struct Private;
std::unique_ptr<Private> d; /* 策略外壳实现的唯一所有权。 */
};
}
@@ -1,14 +1,3 @@
#pragma once
#include "global.hpp"
#include "detail/Throttled_Latest_Only_State.hpp"
namespace aethera {
struct Throttled_Latest_only::Private {
Private(proxy<Periodic_Timer_Service> timer_service,
proxy<FP_Scene> scene,
proxy<FP_Sink> sink);
std::shared_ptr<detail::Throttled_Latest_Only_State> state; /* 帧、借用阶段和停止排空的共享所有权。 */
};
}
+4 -1
View File
@@ -11,7 +11,8 @@ template <typename Self, typename Base> struct Def : Base {
struct Private;
struct Builder {
using Prop = typename Self::Prop;
Builder();
template <typename... Args>
explicit Builder(Args&&... args);
std::unique_ptr<Self> build();
template <Member_Value Member, Assignable_To<Member&> T> Builder& set_prop(Member Prop::* member, T&& value);
std::unique_ptr<Self> ret; /* 构建期间唯一拥有尚未发布的对象。 */
@@ -20,6 +21,8 @@ template <typename Self, typename Base> struct Def : Base {
using Prev_Prop = Base::Prop;
using Prev_Private = Base::Private;
using Prev_Builder = Base::Builder;
template <typename... Args>
explicit Def(Args&&... args);
std::unique_ptr<Private> d; /* 唯一拥有本定义层的并发属性实现。 */
};
}
+12 -4
View File
@@ -5,14 +5,22 @@ namespace aethera {
template <typename Self, typename Base>
struct Def<Self, Base>::Private : Self::Private {
template <typename... Args>
explicit Private(Args&&... args) :
Self::Private(std::forward<Args>(args)...) {}
Import_Struct_With_Dirty<typename Self::Prop> prop;
};
template <typename Self, typename Base>
Def<Self, Base>::Builder::Builder()
: ret(std::make_unique<Self>()) {
ret->d = std::make_unique<Private>();
}
template <typename... Args>
Def<Self, Base>::Builder::Builder(Args&&... args) :
ret(std::make_unique<Self>(std::forward<Args>(args)...)) {}
template <typename Self, typename Base>
template <typename... Args>
Def<Self, Base>::Def(Args&&... args) :
d(std::make_unique<Private>(std::forward<Args>(args)...)) {}
template <typename Self, typename Base>
std::unique_ptr<Self> Def<Self, Base>::Builder::build() {
@@ -1,4 +1,4 @@
#include "function/frame_policy/global.hpp"
#include "function/frame_policy/Throttled_Latest_Only.hpp"
#include <gtest/gtest.h>
@@ -166,10 +166,9 @@ std::unique_ptr<aethera::Throttled_Latest_only> make_policy(
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);
return std::make_unique<aethera::Throttled_Latest_only>(
std::move(timer_service),
std::move(scene),
std::move(sink));
aethera::Throttled_Latest_only::Builder builder(
std::move(timer_service), std::move(scene), std::move(sink));
return builder.build();
}
TEST(Throttled_Latest_Only, Reuses_One_Frame_And_Drops_Busy_Timer_Ticks) {
@@ -177,7 +176,7 @@ TEST(Throttled_Latest_Only, Reuses_One_Frame_And_Drops_Busy_Timer_Ticks) {
auto policy = make_policy(control);
ASSERT_EQ(
policy->start(50.0),
aethera::Start_Throttled_Latest_Only_Result::started);
aethera::Throttled_Latest_only::Start_Result::started);
EXPECT_EQ(control->scheduled_interval, 20ms);
EXPECT_EQ(control->frames_created, 1);
EXPECT_EQ(control->frames_alive, 1);
@@ -204,7 +203,7 @@ TEST(Throttled_Latest_Only, Reuses_One_Frame_And_Drops_Busy_Timer_Ticks) {
policy->stop([&stopped] {
stopped = true;
}),
aethera::Stop_Throttled_Latest_Only_Result::stopping);
aethera::Throttled_Latest_only::Stop_Result::stopping);
EXPECT_EQ(
control->cancelled_timer_id,
Test_Timer_Service::Timer_Id);
@@ -220,7 +219,7 @@ TEST(Throttled_Latest_Only, Stop_Completes_After_Timer_And_Frame_Borrows_Return)
auto policy = make_policy(control);
ASSERT_EQ(
policy->start(60.0),
aethera::Start_Throttled_Latest_Only_Result::started);
aethera::Throttled_Latest_only::Start_Result::started);
control->fire_timer();
bool stopped = false;
@@ -228,7 +227,7 @@ TEST(Throttled_Latest_Only, Stop_Completes_After_Timer_And_Frame_Borrows_Return)
policy->stop([&stopped] {
stopped = true;
}),
aethera::Stop_Throttled_Latest_Only_Result::stopping);
aethera::Throttled_Latest_only::Stop_Result::stopping);
control->complete_cancel();
EXPECT_FALSE(stopped);
EXPECT_EQ(control->frames_alive, 1);
@@ -243,26 +242,27 @@ TEST(Throttled_Latest_Only, Stop_Completes_After_Timer_And_Frame_Borrows_Return)
}
TEST(Throttled_Latest_Only, Reports_Missing_Dependencies_And_Invalid_Fps) {
aethera::Throttled_Latest_only missing_dependencies(
aethera::Throttled_Latest_only::Builder missing_builder(
aethera::proxy<aethera::Periodic_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),
aethera::Start_Throttled_Latest_Only_Result::dependency_unavailable);
missing_dependencies->start(60.0),
aethera::Throttled_Latest_only::Start_Result::dependency_unavailable);
EXPECT_EQ(
missing_dependencies.stop({}),
aethera::Stop_Throttled_Latest_Only_Result::completion_missing);
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);
EXPECT_EQ(
policy->start(0.0),
aethera::Start_Throttled_Latest_Only_Result::invalid_frames_per_second);
aethera::Throttled_Latest_only::Start_Result::invalid_frames_per_second);
EXPECT_EQ(
policy->start(std::numeric_limits<double>::denorm_min()),
aethera::Start_Throttled_Latest_Only_Result::interval_out_of_range);
aethera::Throttled_Latest_only::Start_Result::interval_out_of_range);
}
TEST(Throttled_Latest_Only, Synchronous_Completions_And_Failure_Restore_Frame) {
@@ -272,7 +272,7 @@ TEST(Throttled_Latest_Only, Synchronous_Completions_And_Failure_Restore_Frame) {
auto policy = make_policy(control);
ASSERT_EQ(
policy->start(60.0),
aethera::Start_Throttled_Latest_Only_Result::started);
aethera::Throttled_Latest_only::Start_Result::started);
EXPECT_THROW(control->fire_timer(), std::runtime_error);
control->throw_from_send = false;
@@ -286,7 +286,7 @@ TEST(Throttled_Latest_Only, Synchronous_Completions_And_Failure_Restore_Frame) {
policy->stop([&stopped] {
stopped = true;
}),
aethera::Stop_Throttled_Latest_Only_Result::stopping);
aethera::Throttled_Latest_only::Stop_Result::stopping);
control->complete_cancel();
EXPECT_TRUE(stopped);
}