diff --git a/.gitignore b/.gitignore index 58fbd2d..72c7597 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ /webapp_gallery/.playwright-cli/ /logs/ /deadlock_capture/ +/*.zip diff --git a/start.ps1 b/start.ps1 new file mode 100644 index 0000000..5c2c452 --- /dev/null +++ b/start.ps1 @@ -0,0 +1,52 @@ +$ErrorActionPreference = "Stop" + +$envScript = "D:\ae\proj\Renderive\render_3D\env.ps1" +$targetExe = "D:\ae\proj\Renderive\cmake-build-vs2022_debug\web_server\Renderive_Web_Server.exe" +$workingDir = Split-Path -Parent $targetExe + +if (-not (Test-Path -LiteralPath $envScript)) +{ + throw "Environment script not found: $envScript" +} + +if (-not (Test-Path -LiteralPath $targetExe)) +{ + throw "Target executable not found: $targetExe" +} + +Write-Host "" +Write-Host "Loading environment:" +Write-Host " $envScript" + +# Dot-source env.ps1 so its environment changes are applied to this PowerShell process. +. $envScript + +# Make sure DLLs next to the executable can also be resolved. +if (($env:Path -split ';') -notcontains $workingDir) +{ + $env:Path = "$workingDir;$env:Path" +} + +Write-Host "" +Write-Host "Working directory:" +Write-Host " $workingDir" +Write-Host "" +Write-Host "Starting:" +Write-Host " $targetExe" +Write-Host "" + +Push-Location $workingDir +try +{ + # Forward any arguments passed to this script to Renderive_Web_Server.exe. + & $targetExe @args + $exitCode = $LASTEXITCODE +} +finally +{ + Pop-Location +} + +Write-Host "" +Write-Host "Renderive_Web_Server exit code: $exitCode" +exit $exitCode \ No newline at end of file diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index f04e3c2..a89e78b 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -1,30 +1,19 @@ -#include #include "Gallery_Plot_Session.h" #include "Gallery_Protocol.h" #include "common/Gallery_Scene_Interface.h" #include "render_2D/Gallery_Scene2D.h" #include "render_3D/Gallery_Scene3D.h" -#include -#include #include -#include #include #include -#include #include -#include #include -#include -#include -#include #include #include #include #include -#include #include #include -#include namespace renderive::web { namespace { Gallery_View_Input gallery_input(const Event& event) { @@ -124,78 +113,14 @@ Gallery_Key_Input gallery_input(const Key_Event& event) { input.auto_repeat = event.auto_repeat; return input; } -std::unique_ptr make_gallery_scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) { +std::unique_ptr make_gallery_scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode) { if (case_id.starts_with("datoviz_")) - return make_gallery_scene_3d(session_id, std::move(case_id), mode, automatic_low_latency); - return make_gallery_scene_2d(session_id, std::move(case_id), mode, automatic_low_latency); -} -trantor::EventLoop* automatic_render_loop() { - static trantor::EventLoopThread thread("RenderiveAutomaticRender"); - static const bool started = [] { - thread.run(); - return true; - }(); - static_cast(started); - return thread.getLoop(); + return make_gallery_scene_3d(session_id, std::move(case_id), mode); + return make_gallery_scene_2d(session_id, std::move(case_id), mode); } } -namespace detail { -class Automatic_Render_Executor final { -public: - Automatic_Render_Executor() { - const std::size_t worker_count = renderive::scheduling::scheduler_concurrency(); - workers_.reserve(worker_count); - for (std::size_t index = 0; index < worker_count; ++index) { - workers_.emplace_back([this](std::stop_token stop) { - run(stop); - }); - } - } - ~Automatic_Render_Executor() { - for (auto& worker : workers_) - worker.request_stop(); - condition_.notify_all(); - } - void enqueue(std::function task) { - { - std::lock_guard lock(mutex_); - tasks_.push_back(std::move(task)); - } - condition_.notify_one(); - } -private: - void run(std::stop_token stop) { - for (;;) { - std::function task; - { - std::unique_lock lock(mutex_); - if (!condition_.wait(lock, stop, [this] { - return !tasks_.empty(); - })) - return; - task = std::move(tasks_.front()); - tasks_.pop_front(); - } - task(); - } - } - std::mutex mutex_; - std::condition_variable_any condition_; - std::deque> tasks_; - std::vector workers_; -}; -Automatic_Render_Executor& automatic_render_executor() { - static Automatic_Render_Executor executor; - return executor; -} -} -struct Gallery_Plot_Session::Impl : std::enable_shared_from_this { - using Clock = std::chrono::steady_clock; - explicit Impl(bool enable_automatic_low_latency) - : automatic_low_latency(enable_automatic_low_latency), session_id(next_session_id()) {} - ~Impl() { - disarm_automatic_render(); - } +struct Gallery_Plot_Session::Impl { + Impl() : session_id(next_session_id()) {} static std::uint64_t next_session_id() noexcept { static std::atomic next{1}; return next.fetch_add(1, std::memory_order_relaxed); @@ -263,105 +188,9 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this acquire_foreground_lock() { return std::unique_lock(mutex); } - void disarm_automatic_render() { - ++automatic_timer_revision; - if (automatic_timer == trantor::InvalidTimerId) - return; - automatic_render_loop()->invalidateTimer(automatic_timer); - automatic_timer = trantor::InvalidTimerId; - } - void arm_automatic_render_locked() { - if (!automatic_low_latency) - return; - disarm_automatic_render(); - if (!scene || !scene->can_render_automatically() || render_task_pending) - return; - const auto interval = std::chrono::nanoseconds( - std::max(1, scene->kernel_refresh_interval_ns())); - const auto deadline = last_render_started ? *last_render_started + interval : Clock::now(); - const auto delay = std::max(Clock::duration::zero(), deadline - Clock::now()); - const std::uint64_t revision = automatic_timer_revision; - automatic_timer = automatic_render_loop()->runAfter( - std::chrono::duration(delay).count(), - [session = weak_from_this(), revision] { - if (auto value = session.lock()) - value->automatic_render_due(revision); - }); - } - void automatic_render_due(std::uint64_t revision) { - std::uint64_t generation{}; - { - std::lock_guard lock(mutex); - if (revision != automatic_timer_revision) - return; - automatic_timer = trantor::InvalidTimerId; - ++automatic_timer_revision; - 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 { - detail::automatic_render_executor().enqueue([self = std::move(self), generation] { - self->run_automatic_render(generation); - }); - } catch (...) { - std::lock_guard lock(mutex); - render_task_pending = false; - automatic_render_exception = ::renderive::error::capture( - "arming automatic gallery render", std::current_exception()); - disarm_automatic_render(); - } - } - void run_automatic_render(std::uint64_t generation) { - const auto started = Clock::now(); - try { - std::lock_guard lock(mutex); - if (generation == scene_generation && scene && scene->can_render_automatically()) { - const auto error = scene->render_latest_frame(); - if (error == Gallery_Render_Result::none) - last_render_started = started; - } - render_task_pending = false; - arm_automatic_render_locked(); - } catch (...) { - std::lock_guard lock(mutex); - render_task_pending = false; - automatic_render_exception = ::renderive::error::capture( - "running automatic gallery render", std::current_exception()); - disarm_automatic_render(); - } - } - static bool affects_render_schedule(const Web_Event& event) { - return std::visit( - [](const auto& value) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return false; - } - else if constexpr (std::is_same_v) { - return value.kind != Gallery_Request_Kind::Catalog && - value.kind != Gallery_Request_Kind::Observe && - value.kind != Gallery_Request_Kind::Refresh && - value.kind != Gallery_Request_Kind::Reset_Monitoring; - } - else { - return true; - } - }, - event); - } std::unique_ptr scene; std::mutex mutex; - std::optional last_render_started; - std::uint64_t scene_generation{}; - bool automatic_low_latency{}; - bool render_task_pending{}; - std::exception_ptr automatic_render_exception; std::uint64_t session_id{}; - trantor::TimerId automatic_timer{trantor::InvalidTimerId}; - std::uint64_t automatic_timer_revision{}; std::optional handle_gallery(const Gallery_Request& request) { if (request.kind == Gallery_Request_Kind::Catalog) return Web_Response{Web_Response_Type::Json, Gallery_Protocol::catalog_json()}; @@ -372,10 +201,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_thiscase_id, open->frame_mode, automatic_low_latency); - ++scene_generation; - last_render_started.reset(); - arm_automatic_render_locked(); + scene = make_gallery_scene(session_id, open->case_id, open->frame_mode); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(), @@ -390,8 +216,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this(update_client_metrics(request.message)); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::observer_json( @@ -399,8 +224,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this(update_client_metrics(request.message)); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls( @@ -443,8 +267,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_thiscase_id(), scene->frame_mode(), - action->id)) + if (!Gallery_Protocol::action_available(scene->case_id(), action->id)) return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::error_json( @@ -453,10 +276,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_thisid == "reset") { const std::string id = scene->case_id(); 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(); + scene = make_gallery_scene(session_id, id, mode); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls(id, scene->controls().dump(), @@ -485,7 +305,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this pixels; { auto lock = acquire_foreground_lock(); - if (!scene) + if (!scene || !scene->request_frame()) return std::nullopt; const auto encode_started = std::chrono::steady_clock::now(); pixels = scene->encode_latest_pixels(); @@ -499,55 +319,36 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this handle(const Web_Event& event) { - { - std::exception_ptr exception; - { - std::lock_guard lock(mutex); - exception = std::exchange(automatic_render_exception, {}); - } - if (exception) - ::renderive::error::unexpected( - "automatic gallery render", exception); - } if (std::holds_alternative(event)) return handle_frame_request(); - const bool reschedule = affects_render_schedule(event); - std::optional response; - { - auto lock = acquire_foreground_lock(); - response = std::visit( - [this](const auto& value) -> std::optional { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return handle_gallery(value); - } - else if constexpr (std::is_same_v) { - return std::nullopt; - } - else if constexpr (std::is_same_v) { - if (scene) - scene->resize(value.size.width, value.size.height); - } - else if constexpr (std::is_same_v) { - if (scene) - scene->dispatch(Gallery_Input_Event{gallery_input(value)}); - } - else if constexpr (Event_Object) { - if (scene) - scene->dispatch(Gallery_Input_Event{gallery_input(value)}); - } + auto lock = acquire_foreground_lock(); + return std::visit( + [this](const auto& value) -> std::optional { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return handle_gallery(value); + } + else if constexpr (std::is_same_v) { return std::nullopt; - }, - event); - if (reschedule) - arm_automatic_render_locked(); - } - return response; + } + else if constexpr (std::is_same_v) { + if (scene) + scene->resize(value.size.width, value.size.height); + } + else if constexpr (std::is_same_v) { + if (scene) + scene->dispatch(Gallery_Input_Event{gallery_input(value)}); + } + else if constexpr (Event_Object) { + if (scene) + scene->dispatch(Gallery_Input_Event{gallery_input(value)}); + } + return std::nullopt; + }, + event); } }; -Gallery_Plot_Session::Gallery_Plot_Session() : Gallery_Plot_Session(false) {} -Gallery_Plot_Session::Gallery_Plot_Session(bool automatic_low_latency) - : impl_(std::make_shared(automatic_low_latency)) {} +Gallery_Plot_Session::Gallery_Plot_Session() : impl_(std::make_shared()) {} Gallery_Plot_Session::~Gallery_Plot_Session() = default; std::optional Gallery_Plot_Session::handle(const Web_Event& event) { return impl_->handle(event); diff --git a/web_server/app/Gallery_Plot_Session.h b/web_server/app/Gallery_Plot_Session.h index 8fc58c6..bb38961 100644 --- a/web_server/app/Gallery_Plot_Session.h +++ b/web_server/app/Gallery_Plot_Session.h @@ -10,7 +10,6 @@ namespace renderive::web { class Gallery_Plot_Session final { public: Gallery_Plot_Session(); - explicit Gallery_Plot_Session(bool automatic_low_latency); ~Gallery_Plot_Session(); Gallery_Plot_Session(const Gallery_Plot_Session&) = delete; Gallery_Plot_Session& operator=(const Gallery_Plot_Session&) = delete; diff --git a/web_server/app/Gallery_Protocol.cpp b/web_server/app/Gallery_Protocol.cpp index 3401aef..ea40124 100644 --- a/web_server/app/Gallery_Protocol.cpp +++ b/web_server/app/Gallery_Protocol.cpp @@ -56,7 +56,7 @@ Json parse_object(std::string_view text) { return value.is_object() ? std::move(value) : Json::object(); } Json actions() { - return Json::array({ + Json result = Json::array({ {{"id", "frame_strategy_low_latency"}, {"label", "Low latency"}, {"api", "Renderable_Editor::replace_frame_control_strategy"}, {"group", "Frame strategy"}, {"request_frame", true}}, @@ -65,18 +65,25 @@ Json actions() { {"group", "Frame strategy"}, {"request_frame", true}}, {{"id", "frame_strategy_playback"}, {"label", "Playback"}, {"api", "Renderable_Editor::replace_frame_control_strategy"}, - {"group", "Frame strategy"}, {"request_frame", true}}, - {{"id", "mode_render"}, {"label", "Render frame"}, - {"api", "Scene::render"}, {"group", "Frame"}, {"request_frame", true}}, - {{"id", "capture_next_frame"}, {"label", "Capture next frame"}, - {"api", "Scene_Base::capture_next_frame"}, {"group", "Performance Capture"}, - {"request_frame", true}}, - {{"id", "capture_frames"}, {"label", "Capture N frames"}, - {"api", "Scene_Base::capture_frames"}, {"group", "Performance Capture"}, - {"argument_input", "number"}, {"argument_default", 20}, {"request_frame", true}}, - {{"id", "reset"}, {"label", "Reset scene"}, - {"api", "Scene::Builder"}, {"group", "Scene"}, {"request_frame", true}} + {"group", "Frame strategy"}, {"request_frame", true}} }); + result.push_back({ + {"id", "capture_next_frame"}, {"label", "Capture next frame"}, + {"api", "Scene_Base::capture_next_frame"}, + {"group", "Performance Capture"}, {"request_frame", true} + }); + result.push_back({ + {"id", "capture_frames"}, {"label", "Capture N frames"}, + {"api", "Scene_Base::capture_frames"}, {"group", "Performance Capture"}, + {"argument_input", "number"}, {"argument_default", 20}, + {"request_frame", true} + }); + result.push_back({ + {"id", "reset"}, {"label", "Reset scene"}, + {"api", "Scene::Builder"}, {"group", "Scene"}, + {"request_frame", true} + }); + return result; } template Gallery_Request_Result fail(Gallery_Request_Error error) { @@ -93,7 +100,7 @@ std::string Gallery_Protocol::catalog_json() { {"category", item.category}, {"description", "Live Renderive scene"}, {"order", index}, {"control_count_by_mode", {{"manual", 0}, {"low_latency", 0}, {"playback", 0}}}, - {"action_count_by_mode", {{"manual", 7}, {"low_latency", 7}, {"playback", 7}}} + {"action_count_by_mode", {{"manual", 6}, {"low_latency", 6}, {"playback", 6}}} }); } Json field_mode{{"label", "Mode"}, {"source", "kernel_observer.mode"}}; @@ -119,14 +126,14 @@ std::string Gallery_Protocol::catalog_json() { {"frame_button_label", "Render frame"}}, {{"id", "playback"}, {"title", "Playback"}, {"strategy", "Flow"}, {"description", "Ordered frame playback"}, {"order", 2}, {"accent", "#84b3ce"}, - {"automatic", false}, {"request_after_response", false}, + {"automatic", true}, {"request_after_response", false}, {"request_on_animation_frame", true}, {"observer_visible", true}, {"frame_button_label", "Render frame"}} })}, {"cases", std::move(published_cases)}, {"coverage", {{"case_count", cases.size()}, {"page_count", 1}, {"canvas_count", cases.size()}, {"manual_control_count", 0}, - {"manual_action_count", cases.size() * 7}}}, + {"manual_action_count", cases.size() * 6}}}, {"dashboard", { {"performance", {{"fields", Json::array({ {{"label", "Rendered"}, {"source", "performance.successful_render_count"}, @@ -232,13 +239,13 @@ Gallery_Protocol::control_patch_request(std::string_view message) { } bool Gallery_Protocol::action_available(std::string_view case_id, - Gallery_Frame_Mode, std::string_view action_id) { - return is_case(case_id) && - (action_id == "reset" || action_id == "mode_render" || - action_id == "capture_next_frame" || action_id == "capture_frames" || - action_id == "frame_strategy_low_latency" || - action_id == "frame_strategy_manual" || - action_id == "frame_strategy_playback"); + if (!is_case(case_id)) + return false; + return action_id == "reset" || action_id == "capture_next_frame" || + action_id == "capture_frames" || + action_id == "frame_strategy_low_latency" || + action_id == "frame_strategy_manual" || + action_id == "frame_strategy_playback"; } } // namespace renderive::web diff --git a/web_server/app/Gallery_Protocol.h b/web_server/app/Gallery_Protocol.h index 4aab61b..1c0eefc 100644 --- a/web_server/app/Gallery_Protocol.h +++ b/web_server/app/Gallery_Protocol.h @@ -51,6 +51,6 @@ public: [[nodiscard]] static Gallery_Request_Result action_request(std::string_view message); [[nodiscard]] static Gallery_Request_Result control_patch_request( std::string_view message); - [[nodiscard]] static bool action_available(std::string_view case_id, Gallery_Frame_Mode frame_mode, std::string_view action_id); + [[nodiscard]] static bool action_available(std::string_view case_id, std::string_view action_id); }; } // namespace renderive::web diff --git a/web_server/app/Gallery_WebSocket_Controller.cpp b/web_server/app/Gallery_WebSocket_Controller.cpp index 81a932c..1db2aa2 100644 --- a/web_server/app/Gallery_WebSocket_Controller.cpp +++ b/web_server/app/Gallery_WebSocket_Controller.cpp @@ -14,7 +14,7 @@ namespace renderive::web { void Gallery_WebSocket_Controller::handleNewConnection( const drogon::HttpRequestPtr&, const drogon::WebSocketConnectionPtr& connection) { - connection->setContext(std::make_shared(true)); + connection->setContext(std::make_shared()); connection->setPingMessage("renderive-gallery", std::chrono::seconds(20)); LOG_INFO << "Renderive Gallery WebSocket connected: " << connection->peerAddr().toIpPort(); diff --git a/web_server/app/common/Gallery_Scene_Interface.h b/web_server/app/common/Gallery_Scene_Interface.h index a738691..ebc1baa 100644 --- a/web_server/app/common/Gallery_Scene_Interface.h +++ b/web_server/app/common/Gallery_Scene_Interface.h @@ -6,20 +6,10 @@ #include #include #include -#include #include #include #include namespace renderive::web { -enum class Gallery_Render_Result : std::uint8_t { - none, - inactive, - frame_unavailable, - cancelled, - deadline_exceeded, - backend_failure, - shutting_down -}; class Gallery_Scene_Interface { public: virtual ~Gallery_Scene_Interface() = default; @@ -27,13 +17,11 @@ public: [[nodiscard]] virtual nlohmann::json controls() const = 0; [[nodiscard]] virtual Gallery_Frame_Mode frame_mode() const noexcept = 0; virtual void reset_monitoring() = 0; - [[nodiscard]] virtual bool can_render_automatically() const noexcept = 0; - [[nodiscard]] virtual std::uint64_t kernel_refresh_interval_ns() const = 0; virtual void set_client_metrics(Gallery_Client_Performance metrics) noexcept = 0; [[nodiscard]] virtual adminive::Update_Result apply_patch(std::string_view target, const nlohmann::json& patch) = 0; virtual void resize(int width, int height) = 0; virtual void dispatch(const Gallery_Input_Event& event) = 0; - [[nodiscard]] virtual Gallery_Render_Result render_latest_frame() = 0; + [[nodiscard]] virtual bool request_frame() = 0; [[nodiscard]] virtual std::optional encode_latest_pixels() = 0; virtual void record_pixel_response(std::chrono::steady_clock::time_point request_started, std::chrono::steady_clock::time_point encode_started, std::chrono::steady_clock::time_point encode_finished, std::size_t pixel_bytes) = 0; [[nodiscard]] virtual std::string action(const Gallery_Action_Request& request, bool& recognized) = 0; diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index 67f7e3a..004a070 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -70,9 +70,8 @@ Time_Of_Day current_time_of_day() { class Gallery_Scene2D final : public Gallery_Scene_Interface { public: - Gallery_Scene2D(std::string case_id, Gallery_Frame_Mode mode, - bool automatic) - : case_id_(std::move(case_id)), mode_(mode), automatic_(automatic) { + Gallery_Scene2D(std::string case_id, Gallery_Frame_Mode mode) + : case_id_(std::move(case_id)), mode_(mode) { rebuild(); } @@ -93,14 +92,6 @@ public: Gallery_Frame_Mode frame_mode() const noexcept override { return mode_; } void reset_monitoring() override { render_count_ = 0; } - bool can_render_automatically() const noexcept override { - return automatic_ && mode_ == Gallery_Frame_Mode::Low_Latency; - } - - std::uint64_t kernel_refresh_interval_ns() const override { - return scene_->observer().next_refresh_interval_ns; - } - void set_client_metrics(Gallery_Client_Performance metrics) noexcept override { client_ = metrics; } @@ -129,18 +120,15 @@ public: }, event); } - Gallery_Render_Result render_latest_frame() override { + bool request_frame() override { update_samples(); const auto result = scene_->render_frame(true); - if (!result) return Gallery_Render_Result::backend_failure; - if (*result == Plot_Render_Status::view_inactive) - return Gallery_Render_Result::inactive; - if (*result != Plot_Render_Status::rendered) - return Gallery_Render_Result::frame_unavailable; + if (!result || *result != Plot_Render_Status::rendered) + return false; if (scene_->wait_for_render() != Scene_Render_Result::none) - return Gallery_Render_Result::backend_failure; + return false; ++render_count_; - return Gallery_Render_Result::none; + return true; } std::optional encode_latest_pixels() override { @@ -178,14 +166,9 @@ public: mode_ = next; return "frame strategy replaced"; } - if (request.id == "mode_cycle" || request.id == "mode_render") { - static_cast(render_latest_frame()); - return "frame rendered"; - } if (request.id == "capture_next_frame") { const auto capture = scene_->capture_next_frame(); - if (capture) static_cast(render_latest_frame()); - return capture ? "frame captured" : "capture request rejected"; + return capture ? "capture request accepted" : "capture request rejected"; } if (request.id == "capture_frames") { std::size_t count = 20; @@ -193,7 +176,6 @@ public: count = static_cast(std::clamp( std::get(*request.argument), 1.0, 1000.0)); const auto capture = scene_->capture_frames(count); - if (capture) static_cast(render_latest_frame()); return capture ? "frame capture started" : "capture request rejected"; } recognized = false; @@ -379,9 +361,6 @@ private: } scene_->activate_view(); - update_samples(); - static_cast(scene_->render_frame(true)); - static_cast(scene_->wait_for_render()); } void update_samples() { @@ -437,7 +416,6 @@ private: std::string case_id_; Gallery_Frame_Mode mode_; - bool automatic_{}; Size viewport_{560, 320}; std::unique_ptr scene_; renderive_Owner frequency_axis_; @@ -458,9 +436,7 @@ private: } // namespace std::unique_ptr make_gallery_scene_2d( - std::uint64_t, std::string case_id, Gallery_Frame_Mode mode, - bool automatic_low_latency) { - return std::make_unique( - std::move(case_id), mode, automatic_low_latency); + std::uint64_t, std::string case_id, Gallery_Frame_Mode mode) { + return std::make_unique(std::move(case_id), mode); } } // namespace renderive::web diff --git a/web_server/app/render_2D/Gallery_Scene2D.h b/web_server/app/render_2D/Gallery_Scene2D.h index 5f86024..3baa242 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.h +++ b/web_server/app/render_2D/Gallery_Scene2D.h @@ -4,5 +4,5 @@ #include #include namespace renderive::web { -[[nodiscard]] std::unique_ptr make_gallery_scene_2d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency); +[[nodiscard]] std::unique_ptr make_gallery_scene_2d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode); } diff --git a/web_server/app/render_3D/Gallery_Scene3D.cpp b/web_server/app/render_3D/Gallery_Scene3D.cpp index 13ed625..b4e4eda 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.cpp +++ b/web_server/app/render_3D/Gallery_Scene3D.cpp @@ -69,9 +69,8 @@ Visual_Family visual_family(std::string_view case_id) { class Gallery_Scene3D final : public Gallery_Scene_Interface { public: - Gallery_Scene3D(std::string case_id, Gallery_Frame_Mode mode, - bool automatic) - : case_id_(std::move(case_id)), mode_(mode), automatic_(automatic) { + Gallery_Scene3D(std::string case_id, Gallery_Frame_Mode mode) + : case_id_(std::move(case_id)), mode_(mode) { rebuild(); } @@ -86,12 +85,6 @@ public: Gallery_Frame_Mode frame_mode() const noexcept override { return mode_; } void reset_monitoring() override { render_count_ = 0; } - bool can_render_automatically() const noexcept override { - return automatic_ && mode_ == Gallery_Frame_Mode::Low_Latency; - } - std::uint64_t kernel_refresh_interval_ns() const override { - return scene_->observer().next_refresh_interval_ns; - } void set_client_metrics(Gallery_Client_Performance metrics) noexcept override { client_ = metrics; } @@ -109,11 +102,11 @@ public: } void dispatch(const Gallery_Input_Event&) override {} - Gallery_Render_Result render_latest_frame() override { + bool request_frame() override { if (scene_->request_frame() != Frame_Request_Result::none) - return Gallery_Render_Result::backend_failure; + return false; ++render_count_; - return Gallery_Render_Result::none; + return true; } std::optional encode_latest_pixels() override { @@ -152,14 +145,9 @@ public: mode_ = next; return "frame strategy replaced"; } - if (request.id == "mode_cycle" || request.id == "mode_render") { - static_cast(render_latest_frame()); - return "frame rendered"; - } if (request.id == "capture_next_frame") { const auto capture = scene_->capture_next_frame(); - if (capture) static_cast(render_latest_frame()); - return capture ? "frame captured" : "capture request rejected"; + return capture ? "capture request accepted" : "capture request rejected"; } if (request.id == "capture_frames") { std::size_t count = 20; @@ -167,7 +155,6 @@ public: count = static_cast(std::clamp( std::get(*request.argument), 1.0, 1000.0)); const auto capture = scene_->capture_frames(count); - if (capture) static_cast(render_latest_frame()); return capture ? "frame capture started" : "capture request rejected"; } recognized = false; @@ -203,12 +190,10 @@ private: state.visual_family = visual_family(case_id_); scene_ = Render_Scene_3D::Builder{state}.build( visual_, make_strategy(mode_)); - static_cast(scene_->request_frame()); } std::string case_id_; Gallery_Frame_Mode mode_; - bool automatic_{}; Extent extent_{560, 320}; std::shared_ptr visual_; std::unique_ptr scene_; @@ -218,9 +203,7 @@ private: } // namespace std::unique_ptr make_gallery_scene_3d( - std::uint64_t, std::string case_id, Gallery_Frame_Mode mode, - bool automatic_low_latency) { - return std::make_unique( - std::move(case_id), mode, automatic_low_latency); + std::uint64_t, std::string case_id, Gallery_Frame_Mode mode) { + return std::make_unique(std::move(case_id), mode); } } // namespace renderive::web diff --git a/web_server/app/render_3D/Gallery_Scene3D.h b/web_server/app/render_3D/Gallery_Scene3D.h index 90d7889..238a70b 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.h +++ b/web_server/app/render_3D/Gallery_Scene3D.h @@ -4,5 +4,5 @@ #include #include namespace renderive::web { -[[nodiscard]] std::unique_ptr make_gallery_scene_3d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency); +[[nodiscard]] std::unique_ptr make_gallery_scene_3d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode); } diff --git a/web_server/tests/Datoviz_Gallery_Tests.cpp b/web_server/tests/Datoviz_Gallery_Tests.cpp index 9745a4e..ee98e09 100644 --- a/web_server/tests/Datoviz_Gallery_Tests.cpp +++ b/web_server/tests/Datoviz_Gallery_Tests.cpp @@ -46,9 +46,9 @@ TEST(RenderiveWebDatovizGallery, RgbaEncoderPreservesRowsAndRemovesPadding) { TEST(RenderiveWebDatovizGallery, CurrentThreeDimensionalSceneRendersRgba) { try { auto scene = make_gallery_scene_3d( - 1, "datoviz_3d", Gallery_Frame_Mode::Low_Latency, false); + 1, "datoviz_3d", Gallery_Frame_Mode::Manual); scene->resize(320, 200); - ASSERT_EQ(scene->render_latest_frame(), Gallery_Render_Result::none); + ASSERT_TRUE(scene->request_frame()); const auto frame = scene->encode_latest_pixels(); ASSERT_TRUE(frame); ASSERT_GE(frame->size(), pixel_frame_header_size); diff --git a/web_server/tests/Web_Bridge_Tests.cpp b/web_server/tests/Web_Bridge_Tests.cpp index 1bd95d5..6bde7b2 100644 --- a/web_server/tests/Web_Bridge_Tests.cpp +++ b/web_server/tests/Web_Bridge_Tests.cpp @@ -92,6 +92,10 @@ TEST(RenderiveWebGallery, TwoDimensionalGalleryPublishesStateAndPixels) { const auto& observer = state.at("telemetry").at("kernel_observer"); EXPECT_EQ(observer.at("state").at("viewport").at("width"), 560); EXPECT_EQ(observer.at("renderable_count"), 4U); + EXPECT_EQ(state.at("actions").at("data").size(), 6U); + const auto low_latency_pixels = session.handle(Frame_Request{}); + ASSERT_TRUE(low_latency_pixels); + EXPECT_EQ(low_latency_pixels->type, Web_Response_Type::Pixels); const auto switched = session.handle(Gallery_Request{ Gallery_Request_Kind::Action, @@ -102,28 +106,28 @@ TEST(RenderiveWebGallery, TwoDimensionalGalleryPublishesStateAndPixels) { EXPECT_EQ(switched_state.at("telemetry").at("kernel_observer").at("mode"), "manual"); - const std::string render = - R"({"category":"event","type":"gallery_action","action":"mode_render"})"; - const auto rendered = session.handle( - Gallery_Request{Gallery_Request_Kind::Action, render}); + const auto rendered = session.handle(Frame_Request{}); ASSERT_TRUE(rendered); - EXPECT_EQ(nlohmann::json::parse(rendered->payload).at("type"), "case_state"); + EXPECT_EQ(rendered->type, Web_Response_Type::Pixels); const std::string capture = R"({"category":"event","type":"gallery_action","action":"capture_next_frame"})"; const auto captured = session.handle( Gallery_Request{Gallery_Request_Kind::Action, capture}); ASSERT_TRUE(captured); - const auto capture_state = nlohmann::json::parse(captured->payload); - const auto& sessions = capture_state.at("controls") - .at("performance_capture").at("sessions"); - ASSERT_EQ(sessions.size(), 1U); - EXPECT_EQ(sessions.front().at("captured_count"), 1U); - const auto pixels = session.handle(Frame_Request{}); ASSERT_TRUE(pixels); ASSERT_EQ(pixels->type, Web_Response_Type::Pixels); EXPECT_EQ(pixels->payload.substr(0, 4), "RVP2"); + const auto refreshed = session.handle(Gallery_Request{ + Gallery_Request_Kind::Refresh, + R"({"category":"event","type":"gallery_refresh"})"}); + ASSERT_TRUE(refreshed); + const auto capture_state = nlohmann::json::parse(refreshed->payload); + const auto& sessions = capture_state.at("controls") + .at("performance_capture").at("sessions"); + ASSERT_EQ(sessions.size(), 1U); + EXPECT_EQ(sessions.front().at("captured_count"), 1U); const auto first_pixel = pixels->payload.substr(pixel_frame_header_size, 4); bool contains_drawn_pixel = false; for (std::size_t offset = pixel_frame_header_size; diff --git a/webapp_gallery/src/gallery/plot_card.tsx b/webapp_gallery/src/gallery/plot_card.tsx index 77c65a6..599e211 100644 --- a/webapp_gallery/src/gallery/plot_card.tsx +++ b/webapp_gallery/src/gallery/plot_card.tsx @@ -21,6 +21,6 @@ export function Plot_Card({gallery_case,frame_mode,frame_modes,dashboard,active, return {event.preventDefault();if(snapshot.ready)on_open_inspector({session});}}> } action={}/> {frame_mode.observer_visible&&}{gallery_case.description}{snapshot.notice&&{snapshot.notice}} - {snapshot.controls.length} 属性{snapshot.actions.length} 动作{snapshot.frame_count} 像素帧 + {snapshot.controls.length} 属性{snapshot.actions.length} 动作{snapshot.frame_count} 像素帧{session.frame_mode.id==="manual"&&} ; } diff --git a/webapp_gallery/src/session/gallery_plot_session.ts b/webapp_gallery/src/session/gallery_plot_session.ts index 2042d43..fceba70 100644 --- a/webapp_gallery/src/session/gallery_plot_session.ts +++ b/webapp_gallery/src/session/gallery_plot_session.ts @@ -36,7 +36,7 @@ export class Gallery_Plot_Session { private receive_json(raw: string): void {try{const response=parse_gallery_response(raw);if(!response)return;if(response.type==="error"){this.update({notice:Object.values(response.field_errors??{})[0]??response.message});return;}if(response.type==="observer_state"){this.update({telemetry:response.telemetry,performance_capture:response.telemetry.performance_capture??this.snapshot.performance_capture,frame_count:this.received_frames});return;}if(response.type==="case_state"||response.type==="refresh_state"){const controls=response.controls;const mode_id=typeof response.frame_mode==="string"?response.frame_mode:undefined;const mode=this.frame_modes.find(value=>value.id===mode_id);if(mode)this.current_frame_mode=mode;this.update({ready:true,controls:build_controls(controls?.resources),actions:response.actions?.data??[],telemetry:response.telemetry??{},render_plan:controls?.render_plan??null,performance_capture:controls?.performance_capture??response.telemetry?.performance_capture??null,notice:response.notice??"",frame_count:this.received_frames,protocol_error:""});if(this.stream_active()){this.socket.send("show");this.request_frame(performance.now());}}}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"协议解析失败"});this.socket.close();}} private receive_pixels(buffer: ArrayBuffer): void {try{const now=performance.now();this.frame_controller.receive_frame(now);const accepted=this.presenter.accept(buffer);this.received_frames++;this.client_performance.record_pixel(now,accepted.sequence,accepted.overwritten);}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"像素协议错误"});this.socket.close();}} tick(now: number): void {if(!this.stream_active())return;const presented=this.presenter.present();if(presented)this.client_performance.record_presentation(now);if(this.frame_mode.request_on_animation_frame||(this.frame_mode.request_after_response&&presented))this.request_frame(now);if(this.snapshot.ready&&now-this.last_observe_at>=650){this.last_observe_at=now;this.socket.send("gallery_observe",{client_metrics:this.client_performance.snapshot(now,this.socket.buffered_bytes()) as unknown as Json_Value});}} - request_frame(now=performance.now(), explicit=false): boolean {if(!this.snapshot.ready||!this.socket.is_open()||(!explicit&&!this.stream_active())||(explicit&&!this.visible)||(!explicit&&!this.frame_mode.automatic))return false;return this.frame_controller.request_frame(now);} + request_frame(now=performance.now(), explicit=false): boolean {if(!this.snapshot.ready||!this.socket.is_open()||(!explicit&&!this.stream_active())||(explicit&&(!this.visible||this.frame_mode.id!=="manual"))||(!explicit&&!this.frame_mode.automatic))return false;return this.frame_controller.request_frame(now);} resize(width: number,height: number): void {width=Math.max(240,Math.round(width));height=Math.max(180,Math.round(height));if(width===this.sent_width&&height===this.sent_height)return;this.sent_width=width;this.sent_height=height;if(this.socket.is_open())this.socket.send("resize",{width,height});} pointer(type: "pointer_move"|"pointer_press"|"pointer_release", payload: Record): void {this.socket.send(type,payload);} leave(): void {this.socket.send("leave");}