更新
This commit is contained in:
@@ -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; /* 帧、借用阶段和停止排空的共享所有权。 */
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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; /* 唯一拥有本定义层的并发属性实现。 */
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user