This commit is contained in:
2026-08-15 19:58:39 +08:00
parent 8bfaeeb265
commit 26bbc29a16
29 changed files with 622 additions and 201 deletions
+136 -80
View File
@@ -3,6 +3,7 @@
#include "common/Gallery_Scene_Interface.h"
#include "render_2D/Gallery_Scene2D.h"
#include "render_3D/Gallery_Scene3D.h"
#include <renderive/scheduling/Scheduler.hpp>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <atomic>
@@ -10,12 +11,13 @@
#include <cmath>
#include <condition_variable>
#include <cstdint>
#include <limits>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <type_traits>
#include <utility>
namespace renderive::web {
@@ -123,26 +125,84 @@ std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene(std::uint64_t sessio
return make_gallery_scene_2d(session_id, std::move(case_id), mode, automatic_low_latency);
}
}
struct Gallery_Plot_Session::Impl {
struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Session::Impl> {
using Clock = std::chrono::steady_clock;
class Automatic_Render_Scheduler final {
public:
static Automatic_Render_Scheduler& instance() {
static Automatic_Render_Scheduler scheduler;
return scheduler;
}
void arm(std::uint64_t session_id, std::weak_ptr<Impl> session, Clock::time_point deadline) {
std::lock_guard lock(mutex_);
if (const auto current = entries_.find(session_id); current != entries_.end()) {
schedule_.erase(current->second);
entries_.erase(current);
}
const auto iterator = schedule_.emplace(deadline, Entry{session_id, std::move(session)});
entries_.emplace(session_id, iterator);
condition_.notify_one();
}
void disarm(std::uint64_t session_id) noexcept {
std::lock_guard lock(mutex_);
const auto current = entries_.find(session_id);
if (current == entries_.end())
return;
schedule_.erase(current->second);
entries_.erase(current);
condition_.notify_one();
}
private:
struct Entry {
std::uint64_t session_id{};
std::weak_ptr<Impl> session;
};
using Schedule = std::multimap<Clock::time_point, Entry>;
Automatic_Render_Scheduler() : thread_([this](std::stop_token stop) { run(stop); }) {}
~Automatic_Render_Scheduler() {
thread_.request_stop();
condition_.notify_all();
}
void run(std::stop_token stop) noexcept {
std::unique_lock lock(mutex_);
while (!stop.stop_requested()) {
if (schedule_.empty()) {
condition_.wait(lock, [this, &stop] {
return stop.stop_requested() || !schedule_.empty();
});
continue;
}
const auto deadline = schedule_.begin()->first;
if (condition_.wait_until(lock, deadline) != std::cv_status::timeout)
continue;
if (schedule_.empty() || schedule_.begin()->first > Clock::now())
continue;
auto iterator = schedule_.begin();
Entry entry = std::move(iterator->second);
entries_.erase(entry.session_id);
schedule_.erase(iterator);
lock.unlock();
if (auto session = entry.session.lock())
session->automatic_render_due();
lock.lock();
}
}
std::mutex mutex_;
std::condition_variable condition_;
Schedule schedule_;
std::unordered_map<std::uint64_t, Schedule::iterator> entries_;
std::jthread thread_;
};
explicit Impl(bool enable_automatic_low_latency)
: automatic_low_latency(enable_automatic_low_latency),
session_id(next_session_id()) {}
: automatic_low_latency(enable_automatic_low_latency), session_id(next_session_id()) {}
~Impl() {
render_worker.request_stop();
scheduler_condition.notify_all();
if (automatic_low_latency)
Automatic_Render_Scheduler::instance().disarm(session_id);
}
static std::uint64_t next_session_id() noexcept {
static std::atomic<std::uint64_t> next{1};
return next.fetch_add(1, std::memory_order_relaxed);
}
void ensure_render_worker() {
if (!automatic_low_latency || render_worker.joinable())
return;
render_worker = std::jthread(
[this](std::stop_token stop) {
run_automatic_renderer(stop);
});
}
bool update_client_metrics(std::string_view message) {
if (!scene)
return false;
@@ -204,57 +264,59 @@ struct Gallery_Plot_Session::Impl {
return true;
}
[[nodiscard]] std::unique_lock<std::mutex> acquire_foreground_lock() {
foreground_waiters.fetch_add(1, std::memory_order_release);
scheduler_condition.notify_all();
std::unique_lock lock(mutex);
foreground_waiters.fetch_sub(1, std::memory_order_release);
scheduler_condition.notify_all();
return lock;
return std::unique_lock<std::mutex>(mutex);
}
void run_automatic_renderer(std::stop_token stop) {
using Clock = std::chrono::steady_clock;
std::unique_lock lock(mutex);
std::optional<Clock::time_point> last_render_started;
std::uint64_t active_generation = std::numeric_limits<std::uint64_t>::max();
while (!stop.stop_requested()) {
scheduler_condition.wait(lock, [this, &stop] {
return stop.stop_requested() || (scene && scene->can_render_automatically());
void arm_automatic_render_locked() noexcept {
if (!automatic_low_latency)
return;
auto& scheduler = Automatic_Render_Scheduler::instance();
if (!scene || !scene->can_render_automatically() || render_task_pending) {
scheduler.disarm(session_id);
return;
}
try {
const auto interval = std::chrono::nanoseconds(
std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
const auto deadline = last_render_started ? *last_render_started + interval : Clock::now();
scheduler.arm(session_id, weak_from_this(), deadline);
} catch (...) {
scheduler.disarm(session_id);
}
}
void automatic_render_due() {
std::uint64_t generation{};
{
std::lock_guard lock(mutex);
if (!automatic_low_latency || !scene || !scene->can_render_automatically() || render_task_pending)
return;
render_task_pending = true;
generation = scene_generation;
}
auto self = shared_from_this();
try {
renderive::scheduling::enqueue_task([self = std::move(self), generation] {
self->run_automatic_render(generation);
});
if (stop.stop_requested())
break;
if (active_generation != scene_generation) {
active_generation = scene_generation;
last_render_started.reset();
} catch (...) {
std::lock_guard lock(mutex);
render_task_pending = false;
arm_automatic_render_locked();
}
}
void run_automatic_render(std::uint64_t generation) noexcept {
const auto started = Clock::now();
try {
std::lock_guard lock(mutex);
if (generation == scene_generation && scene && scene->can_render_automatically()) {
(void)scene->render_latest_frame();
last_render_started = started;
}
if (foreground_waiters.load(std::memory_order_acquire) != 0) {
scheduler_condition.wait(lock, [this, &stop] {
return stop.stop_requested() || foreground_waiters.load(std::memory_order_acquire) == 0;
});
continue;
}
const auto revision = scheduler_revision;
const auto interval = std::chrono::nanoseconds(std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
const auto now = Clock::now();
const auto deadline = last_render_started ? *last_render_started + interval : now;
if (now < deadline) {
const auto spin_window = std::min(interval / 4, std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(250)));
const auto coarse_deadline = deadline > now + spin_window ? deadline - spin_window : now;
if (now < coarse_deadline) {
scheduler_condition.wait_until(lock, coarse_deadline, [this, &stop, revision, active_generation] {
return stop.stop_requested() || scheduler_revision != revision || scene_generation != active_generation ||
foreground_waiters.load(std::memory_order_acquire) != 0 || !scene || !scene->can_render_automatically();
});
continue;
}
lock.unlock();
while (!stop.stop_requested() && Clock::now() < deadline)
std::this_thread::yield();
lock.lock();
continue;
}
const auto started = Clock::now();
(void)scene->render_latest_frame();
last_render_started = started;
render_task_pending = false;
arm_automatic_render_locked();
} catch (...) {
std::lock_guard lock(mutex);
render_task_pending = false;
arm_automatic_render_locked();
}
}
static bool affects_render_schedule(const Web_Event& event) {
@@ -278,13 +340,11 @@ struct Gallery_Plot_Session::Impl {
}
std::unique_ptr<Gallery_Scene_Interface> scene;
std::mutex mutex;
std::condition_variable scheduler_condition;
std::uint64_t scheduler_revision{};
std::optional<Clock::time_point> last_render_started;
std::uint64_t scene_generation{};
bool automatic_low_latency{};
bool render_task_pending{};
std::uint64_t session_id{};
std::atomic<std::uint32_t> foreground_waiters{};
std::jthread render_worker;
std::optional<Web_Response> handle_gallery(const Gallery_Request& request) {
if (request.kind == Gallery_Request_Kind::Catalog)
return Web_Response{Web_Response_Type::Json, Gallery_Protocol::catalog_json()};
@@ -297,8 +357,8 @@ struct Gallery_Plot_Session::Impl {
};
scene = make_gallery_scene(session_id, open->case_id, open->frame_mode, automatic_low_latency);
++scene_generation;
if (scene->frame_mode() == Gallery_Frame_Mode::Low_Latency)
ensure_render_worker();
last_render_started.reset();
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(),
@@ -313,10 +373,8 @@ struct Gallery_Plot_Session::Impl {
Gallery_Protocol::error_json("请先发送 gallery_open")
};
if (request.kind == Gallery_Request_Kind::Observe) {
if (update_client_metrics(request.message)) {
++scheduler_revision;
scheduler_condition.notify_all();
}
if (update_client_metrics(request.message))
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::observer_json(
@@ -324,10 +382,8 @@ struct Gallery_Plot_Session::Impl {
};
}
if (request.kind == Gallery_Request_Kind::Refresh) {
if (update_client_metrics(request.message)) {
++scheduler_revision;
scheduler_condition.notify_all();
}
if (update_client_metrics(request.message))
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(
@@ -382,6 +438,8 @@ struct Gallery_Plot_Session::Impl {
const auto mode = scene->frame_mode();
scene = make_gallery_scene(session_id, id, mode, automatic_low_latency);
++scene_generation;
last_render_started.reset();
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(id, scene->controls().dump(),
@@ -455,16 +513,14 @@ struct Gallery_Plot_Session::Impl {
},
event);
if (reschedule)
++scheduler_revision;
arm_automatic_render_locked();
}
if (reschedule)
scheduler_condition.notify_all();
return response;
}
};
Gallery_Plot_Session::Gallery_Plot_Session() : Gallery_Plot_Session(false) {}
Gallery_Plot_Session::Gallery_Plot_Session(bool automatic_low_latency)
: impl_(std::make_unique<Impl>(automatic_low_latency)) {}
: impl_(std::make_shared<Impl>(automatic_low_latency)) {}
Gallery_Plot_Session::~Gallery_Plot_Session() = default;
std::optional<Web_Response> Gallery_Plot_Session::handle(const Web_Event& event) {
return impl_->handle(event);