Files
Aethera/web_server/src/detail/Gallery_Frame_Clock.cpp
T
2026-08-25 21:44:57 +08:00

110 lines
4.2 KiB
C++

#include "Gallery_Frame_Clock.hpp"
#include <atomic>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <utility>
namespace aethera::web::detail {
struct Gallery_Frame_Clock::Private {
std::chrono::nanoseconds interval{};
Tick_Handler tick_handler{};
Failure_Handler failure_handler{};
/* condition_variable_any 的 stop_token 等待和 jthread 替换必须共享生命周期
* 临界区;它不是业务状态锁,双缓冲不能替代等待协议。 */
std::mutex lifecycle_mutex{};
std::condition_variable_any wake{};
std::jthread thread{};
std::atomic_bool running{};
std::atomic_uint64_t generation{}; /* 区分 stop 期间接管的新时钟。 */
Private(double frame_rate, Tick_Handler value_tick_handler,
Failure_Handler value_failure_handler)
: tick_handler(std::move(value_tick_handler)),
failure_handler(std::move(value_failure_handler)) {
if (!std::isfinite(frame_rate) || frame_rate <= 0.0)
throw std::invalid_argument("gallery frame rate must be positive");
interval = std::chrono::nanoseconds{
static_cast<std::int64_t>(std::llround(1'000'000'000.0 / frame_rate))};
if (interval.count() <= 0)
throw std::invalid_argument("gallery frame rate exceeds clock resolution");
if (!tick_handler)
throw std::invalid_argument("gallery frame clock requires a tick handler");
}
void report(std::uint64_t active_generation,
std::exception_ptr failure) noexcept {
if (generation.load(std::memory_order_acquire) == active_generation)
running.store(false, std::memory_order_release);
if (!failure_handler) return;
try { failure_handler(std::move(failure)); }
catch (...) {}
}
void run(std::stop_token stop, std::uint64_t active_generation) noexcept {
const auto origin = std::chrono::steady_clock::now();
std::uint64_t sequence{};
auto deadline = origin + interval;
while (!stop.stop_requested()) {
std::unique_lock lock(lifecycle_mutex);
wake.wait_until(lock, stop, deadline, [] { return false; });
lock.unlock();
if (stop.stop_requested()) break;
try {
++sequence;
tick_handler(Plot_Render_Tick{
std::chrono::steady_clock::now(),
sequence,
std::chrono::duration<double, std::milli>(deadline - origin).count()});
deadline += interval;
const auto now = std::chrono::steady_clock::now();
if (deadline <= now)
deadline += interval * ((now - deadline) / interval + 1);
}
catch (...) {
report(active_generation, std::current_exception());
return;
}
}
if (generation.load(std::memory_order_acquire) == active_generation)
running.store(false, std::memory_order_release);
}
};
Gallery_Frame_Clock::Gallery_Frame_Clock(
double frame_rate, Tick_Handler tick_handler,
Failure_Handler failure_handler)
: d(std::make_shared<Private>(frame_rate, std::move(tick_handler),
std::move(failure_handler))) {}
Gallery_Frame_Clock::~Gallery_Frame_Clock() { stop(); }
void Gallery_Frame_Clock::start() {
std::lock_guard lock(d->lifecycle_mutex);
if (d->running.exchange(true, std::memory_order_acq_rel)) return;
const auto generation = d->generation.fetch_add(
1, std::memory_order_acq_rel) + 1;
d->thread = std::jthread([state = d, generation](std::stop_token stop) {
state->run(stop, generation);
});
}
void Gallery_Frame_Clock::stop() noexcept {
if (!d) return;
std::jthread thread;
{
std::lock_guard lock(d->lifecycle_mutex);
if (!d->running.exchange(false, std::memory_order_acq_rel)) return;
d->thread.request_stop();
d->wake.notify_all();
thread = std::move(d->thread);
}
if (!thread.joinable()) return;
if (thread.get_id() == std::this_thread::get_id()) thread.detach();
else thread.join();
}
}