#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 namespace renderive::web { namespace { Gallery_View_Input gallery_input(const Event& event) { Gallery_View_Input input; switch (event.type) { case Event_Type::Show: input.type = Gallery_View_Input_Type::Show; break; case Event_Type::Hide: input.type = Gallery_View_Input_Type::Hide; break; case Event_Type::Leave: input.type = Gallery_View_Input_Type::Leave; break; default: input.type = Gallery_View_Input_Type::Leave; break; } return input; } Gallery_Mouse_Button gallery_mouse_button(Mouse_Button button) { switch (button) { case Mouse_Button::None: return Gallery_Mouse_Button::None; case Mouse_Button::Left: return Gallery_Mouse_Button::Left; case Mouse_Button::Right: return Gallery_Mouse_Button::Right; case Mouse_Button::Middle: return Gallery_Mouse_Button::Middle; } return Gallery_Mouse_Button::None; } Gallery_Key gallery_key(Key key) { switch (key) { case Key::Escape: return Gallery_Key::Escape; case Key::Enter: return Gallery_Key::Enter; case Key::Space: return Gallery_Key::Space; case Key::Delete: return Gallery_Key::Delete; case Key::Backspace: return Gallery_Key::Backspace; case Key::Left: return Gallery_Key::Left; case Key::Right: return Gallery_Key::Right; case Key::Up: return Gallery_Key::Up; case Key::Down: return Gallery_Key::Down; case Key::Unknown: return Gallery_Key::Unknown; } return Gallery_Key::Unknown; } Gallery_Pointer_Input gallery_input(const Pointer_Event& event) { Gallery_Pointer_Input input; if (event.type == Event_Type::Pointer_Press) input.type = Gallery_Pointer_Input_Type::Press; else if (event.type == Event_Type::Pointer_Release) input.type = Gallery_Pointer_Input_Type::Release; else input.type = Gallery_Pointer_Input_Type::Move; input.x = event.position.x; input.y = event.position.y; input.global_x = event.global_position.x; input.global_y = event.global_position.y; input.button = gallery_mouse_button(event.button); input.buttons = event.buttons; input.modifiers = static_cast(event.modifiers); return input; } Gallery_Wheel_Input gallery_input(const Wheel_Event& event) { Gallery_Wheel_Input input; input.x = event.position.x; input.y = event.position.y; input.global_x = event.global_position.x; input.global_y = event.global_position.y; input.button = gallery_mouse_button(event.button); input.buttons = event.buttons; input.modifiers = static_cast(event.modifiers); input.angle_delta_x = event.angle_delta_x; input.angle_delta_y = event.angle_delta_y; input.pixel_delta_x = event.pixel_delta_x; input.pixel_delta_y = event.pixel_delta_y; return input; } Gallery_Key_Input gallery_input(const Key_Event& event) { Gallery_Key_Input input; input.type = event.type == Event_Type::Key_Press ? Gallery_Key_Input_Type::Press : Gallery_Key_Input_Type::Release; input.key = gallery_key(event.key); input.native_key = event.native_key; input.modifiers = static_cast(event.modifiers); 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) { 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(); } } 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(); } static std::uint64_t next_session_id() noexcept { static std::atomic next{1}; return next.fetch_add(1, std::memory_order_relaxed); } bool update_client_metrics(std::string_view message) { if (!scene) return false; const auto root = nlohmann::json::parse(message, nullptr, false); if (root.is_discarded() || !root.is_object() || !root.contains("client_metrics") || !root["client_metrics"].is_object()) return false; const auto& metrics = root["client_metrics"]; const auto finite_metric = [&metrics](std::string_view name) { const auto iterator = metrics.find(std::string(name)); if (iterator == metrics.end() || !iterator->is_number()) return 0.0; const double value = iterator->get(); return std::isfinite(value) ? value : 0.0; }; std::uint64_t buffered_bytes{}; if (const auto iterator = metrics.find("websocket_buffered_bytes"); iterator != metrics.end() && iterator->is_number_unsigned()) { buffered_bytes = iterator->get(); } const auto unsigned_metric = [&metrics](std::string_view name) { const auto iterator = metrics.find(std::string(name)); if (iterator == metrics.end() || !iterator->is_number_integer()) return std::uint64_t{}; const auto value = iterator->get(); return value > 0 ? static_cast(value) : std::uint64_t{}; }; Gallery_Client_Performance performance; performance.transport_fps = finite_metric("transport_fps"); performance.presentation_fps = finite_metric("presentation_fps"); performance.websocket_buffered_bytes = buffered_bytes; performance.changed_pixel_frames = unsigned_metric("changed_pixel_frames"); performance.duplicate_pixel_frames = unsigned_metric("duplicate_pixel_frames"); performance.frame_request_timeout_count = unsigned_metric("frame_request_timeout_count"); performance.frame_round_trip_ms = finite_metric("frame_round_trip_ms"); performance.frame_round_trip_average_ms = finite_metric("frame_round_trip_average_ms"); performance.frame_round_trip_deviation_ms = finite_metric("frame_round_trip_deviation_ms"); performance.frame_round_trip_p95_ms = finite_metric("frame_round_trip_p95_ms"); performance.frame_round_trip_p99_ms = finite_metric("frame_round_trip_p99_ms"); performance.display_interval_ms = finite_metric("display_interval_ms"); performance.display_interval_average_ms = finite_metric("display_interval_average_ms"); performance.display_interval_latest_ms = finite_metric("display_interval_latest_ms"); performance.display_interval_p95_ms = finite_metric("display_interval_p95_ms"); performance.display_interval_p99_ms = finite_metric("display_interval_p99_ms"); performance.display_interval_deviation_ms = finite_metric("display_interval_deviation_ms"); performance.overwritten_pixel_frames = unsigned_metric("overwritten_pixel_frames"); performance.last_pixel_receive_age_ms = finite_metric("last_pixel_receive_age_ms"); performance.last_pixel_change_age_ms = finite_metric("last_pixel_change_age_ms"); scene->set_client_metrics(performance); return true; } [[nodiscard]] std::unique_lock acquire_foreground_lock() { return std::unique_lock(mutex); } void disarm_automatic_render() noexcept { ++automatic_timer_revision; if (automatic_timer == trantor::InvalidTimerId) return; automatic_render_loop()->invalidateTimer(automatic_timer); automatic_timer = trantor::InvalidTimerId; } void arm_automatic_render_locked() noexcept { if (!automatic_low_latency) return; disarm_automatic_render(); if (!scene || !scene->can_render_automatically() || render_task_pending) return; try { 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); }); } catch (...) { disarm_automatic_render(); } } 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 { renderive::scheduling::enqueue_task([self = std::move(self), generation] { self->run_automatic_render(generation); }); } 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; } 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) { 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::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()}; if (request.kind == Gallery_Request_Kind::Open) { const auto open = Gallery_Protocol::open_request(request.message); if (!open) return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::error_json("未知的 gallery case 或 frame_mode", "case") }; scene = make_gallery_scene(session_id, open->case_id, open->frame_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(scene->case_id(), scene->controls().dump(), scene->telemetry_json(), "Renderive/Kernel 独立画布已创建", scene->frame_mode(), false) }; } if (!scene) return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::error_json("请先发送 gallery_open") }; if (request.kind == Gallery_Request_Kind::Observe) { if (update_client_metrics(request.message)) arm_automatic_render_locked(); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::observer_json( scene->case_id(), scene->frame_mode(), scene->telemetry_json()) }; } if (request.kind == Gallery_Request_Kind::Refresh) { if (update_client_metrics(request.message)) arm_automatic_render_locked(); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls( scene->case_id(), scene->controls().dump(), scene->telemetry_json(), "观察数据已手动刷新", scene->frame_mode(), true) }; } if (request.kind == Gallery_Request_Kind::Reset_Monitoring) { scene->reset_monitoring(); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls( scene->case_id(), scene->controls().dump(), scene->telemetry_json(), "监测滑动窗口已重置", scene->frame_mode(), true) }; } if (request.kind == Gallery_Request_Kind::Patch) { const auto patch = Gallery_Protocol::control_patch_request(request.message); if (!patch) return Web_Response{Web_Response_Type::Json, Gallery_Protocol::error_json("gallery_patch format is invalid")}; const auto result = scene->apply_patch( patch->target, nlohmann::json::parse(patch->patch_json)); if (!result.success) return Web_Response{Web_Response_Type::Json, Gallery_Protocol::error_json(result.message)}; return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(), scene->telemetry_json(), "控件 API 已由后端应用", scene->frame_mode(), false) }; } const auto action = Gallery_Protocol::action_request(request.message); if (!action) return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::error_json("gallery_action 格式无效") }; if (!Gallery_Protocol::action_available(scene->case_id(), scene->frame_mode(), action->id)) return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::error_json( "动作不属于当前画布或帧策略", "action") }; if (action->id == "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(); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls(id, scene->controls().dump(), scene->telemetry_json(), "本图已恢复后端默认值", mode, false) }; } bool recognized{}; const std::string notice = scene->action(*action, recognized); if (!recognized) return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::error_json("动作不属于当前画布或参数无效", "action") }; scene->record_action_notice_if_empty(notice); return Web_Response{ Web_Response_Type::Json, Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(), scene->telemetry_json(), notice, scene->frame_mode(), false) }; } std::optional handle_frame_request() { const auto request_started = std::chrono::steady_clock::now(); std::optional pixels; { auto lock = acquire_foreground_lock(); if (!scene) return std::nullopt; const auto encode_started = std::chrono::steady_clock::now(); pixels = scene->encode_latest_pixels(); const auto encode_finished = std::chrono::steady_clock::now(); if (pixels) scene->record_pixel_response(request_started, encode_started, encode_finished, pixels->size()); } if (!pixels) return std::nullopt; return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)}; } std::optional handle(const Web_Event& event) { 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)}); } return std::nullopt; }, event); if (reschedule) arm_automatic_render_locked(); } 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_shared(automatic_low_latency)) {} Gallery_Plot_Session::~Gallery_Plot_Session() = default; std::optional Gallery_Plot_Session::handle(const Web_Event& event) { return impl_->handle(event); } } // namespace renderive::web