From 224004365c639200f4c8bbdad2f8df8b191f99fd Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Mon, 17 Aug 2026 23:34:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web_server/app/Gallery_Protocol.cpp | 14 +++------- web_server/app/render_2D/Gallery_Scene2D.cpp | 27 ++++++++++++------- web_server/app/render_3D/Gallery_Scene3D.cpp | 27 ++++++++++++------- web_server/tests/Web_Bridge_Tests.cpp | 6 +++++ webapp_gallery/src/protocol/gallery_types.ts | 8 +++--- webapp_gallery/src/runtime/pixel_presenter.ts | 1 + .../src/session/gallery_plot_session.ts | 14 +++++----- 7 files changed, 59 insertions(+), 38 deletions(-) diff --git a/web_server/app/Gallery_Protocol.cpp b/web_server/app/Gallery_Protocol.cpp index ea40124..fbc864a 100644 --- a/web_server/app/Gallery_Protocol.cpp +++ b/web_server/app/Gallery_Protocol.cpp @@ -108,7 +108,7 @@ std::string Gallery_Protocol::catalog_json() { Json field_event{{"label", "Event"}, {"source", "kernel_observer.event"}}; Json root{ {"category", "gallery"}, {"type", "catalog"}, - {"protocol", "renderive.control-gallery"}, {"protocol_version", 5}, + {"protocol", "renderive.control-gallery"}, {"protocol_version", 6}, {"transport", {{"pixels", "websocket-binary-rvp2"}, {"socket_per_canvas", true}}}, {"navigation", {{"default_mode", "low_latency"}, {"all_categories_label", "All"}, {"hero_eyebrow", "Renderive"}, {"hero_title", "Scene Gallery"}, @@ -116,19 +116,13 @@ std::string Gallery_Protocol::catalog_json() { {"frame_modes", Json::array({ {{"id", "manual"}, {"title", "Manual"}, {"strategy", "Manual"}, {"description", "Explicit frame requests"}, {"order", 0}, {"accent", "#ffbd69"}, - {"automatic", false}, {"request_after_response", false}, - {"request_on_animation_frame", false}, {"observer_visible", true}, - {"frame_button_label", "Render frame"}}, + {"observer_visible", true}, {"frame_button_label", "Render frame"}}, {{"id", "low_latency"}, {"title", "Low latency"}, {"strategy", "Low_Latency"}, {"description", "Latest-state rendering"}, {"order", 1}, {"accent", "#59d6c5"}, - {"automatic", true}, {"request_after_response", true}, - {"request_on_animation_frame", false}, {"observer_visible", true}, - {"frame_button_label", "Render frame"}}, + {"observer_visible", true}, {"frame_button_label", "Render frame"}}, {{"id", "playback"}, {"title", "Playback"}, {"strategy", "Flow"}, {"description", "Ordered frame playback"}, {"order", 2}, {"accent", "#84b3ce"}, - {"automatic", true}, {"request_after_response", false}, - {"request_on_animation_frame", true}, {"observer_visible", true}, - {"frame_button_label", "Render frame"}} + {"observer_visible", true}, {"frame_button_label", "Render frame"}} })}, {"cases", std::move(published_cases)}, {"coverage", {{"case_count", cases.size()}, {"page_count", 1}, diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index b2f08e5..88d4251 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -61,6 +61,14 @@ std::string_view mode_id(Gallery_Frame_Mode mode) { return "low_latency"; } +Gallery_Frame_Mode strategy_mode(const Frame_Control_Strategy_Base& strategy) { + if (dynamic_cast(&strategy)) + return Gallery_Frame_Mode::Manual; + if (dynamic_cast(&strategy)) + return Gallery_Frame_Mode::Playback; + return Gallery_Frame_Mode::Low_Latency; +} + Time_Of_Day current_time_of_day() { constexpr std::int64_t day_ms = 24LL * 60LL * 60LL * 1000LL; const auto now = std::chrono::duration_cast( @@ -72,8 +80,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) - : case_id_(std::move(case_id)), mode_(mode) { - rebuild(); + : case_id_(std::move(case_id)) { + rebuild(mode); } ~Gallery_Scene2D() override { @@ -90,7 +98,9 @@ public: gallery_performance_capture_json(*scene_)}}; } - Gallery_Frame_Mode frame_mode() const noexcept override { return mode_; } + Gallery_Frame_Mode frame_mode() const noexcept override { + return strategy_mode(*scene_->frame_control_strategy()); + } void reset_monitoring() override { render_count_ = 0; } void set_client_metrics(Gallery_Client_Performance metrics) noexcept override { @@ -119,8 +129,9 @@ public: void resize(int width, int height) override { const Size requested{std::max(1, width), std::max(1, height)}; if (requested == viewport_) return; + const auto mode = frame_mode(); viewport_ = requested; - rebuild(); + rebuild(mode); } void dispatch(const Gallery_Input_Event& event) override { @@ -178,7 +189,6 @@ public: }); if (operation.wait() != Scene_Edit_Error::none) return "frame strategy replacement rejected"; - mode_ = next; return "frame strategy replaced"; } if (request.id == "capture_next_frame") { @@ -202,7 +212,7 @@ public: std::string telemetry_json() const override { const auto observer = scene_->observer(); auto kernel_observer = observer_json(observer); - kernel_observer["mode"] = mode_id(mode_); + kernel_observer["mode"] = mode_id(frame_mode()); kernel_observer["event"] = "render_completed"; kernel_observer["limit"] = "none"; return nlohmann::json{ @@ -222,12 +232,12 @@ private: .build(std::forward(arguments)...); } - void rebuild() { + void rebuild(Gallery_Frame_Mode mode) { Render_Scene_2D::State scene_state; scene_state.viewport = viewport_; scene_state.background = Color{3, 8, 14, 255}; scene_ = Render_Scene_2D::Builder{scene_state}.build( - make_strategy(mode_)); + make_strategy(mode)); { auto attach = scene_->attach_builder(); @@ -430,7 +440,6 @@ private: } std::string case_id_; - Gallery_Frame_Mode mode_; Size viewport_{560, 320}; std::unique_ptr scene_; renderive_Owner frequency_axis_; diff --git a/web_server/app/render_3D/Gallery_Scene3D.cpp b/web_server/app/render_3D/Gallery_Scene3D.cpp index 9a1d0d0..b2b69b7 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.cpp +++ b/web_server/app/render_3D/Gallery_Scene3D.cpp @@ -50,6 +50,14 @@ std::string_view mode_id(Gallery_Frame_Mode mode) { return "low_latency"; } +Gallery_Frame_Mode strategy_mode(const Frame_Control_Strategy_Base& strategy) { + if (dynamic_cast(&strategy)) + return Gallery_Frame_Mode::Manual; + if (dynamic_cast(&strategy)) + return Gallery_Frame_Mode::Playback; + return Gallery_Frame_Mode::Low_Latency; +} + Visual_Family visual_family(std::string_view case_id) { if (case_id == "datoviz_splat") return Visual_Family::Splat; if (case_id == "datoviz_pixel") return Visual_Family::Pixel; @@ -71,8 +79,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) - : case_id_(std::move(case_id)), mode_(mode) { - rebuild(); + : case_id_(std::move(case_id)) { + rebuild(mode); } const std::string& case_id() const noexcept override { return case_id_; } @@ -84,7 +92,9 @@ public: {"performance_capture", gallery_performance_capture_json(*scene_)}}; } - Gallery_Frame_Mode frame_mode() const noexcept override { return mode_; } + Gallery_Frame_Mode frame_mode() const noexcept override { + return strategy_mode(*scene_->frame_control_strategy()); + } void reset_monitoring() override { render_count_ = 0; } void set_client_metrics(Gallery_Client_Performance metrics) noexcept override { client_ = metrics; @@ -112,8 +122,9 @@ public: static_cast(std::max(1, height))}; if (requested.width == extent_.width && requested.height == extent_.height) return; + const auto mode = frame_mode(); extent_ = requested; - rebuild(); + rebuild(mode); } void dispatch(const Gallery_Input_Event&) override {} @@ -157,7 +168,6 @@ public: }); if (operation.wait() != Scene_Edit_Error::none) return "frame strategy replacement rejected"; - mode_ = next; return "frame strategy replaced"; } if (request.id == "capture_next_frame") { @@ -181,7 +191,7 @@ public: std::string telemetry_json() const override { const auto observer = scene_->observer(); auto kernel_observer = observer_json(observer); - kernel_observer["mode"] = mode_id(mode_); + kernel_observer["mode"] = mode_id(frame_mode()); kernel_observer["event"] = "render_completed"; kernel_observer["limit"] = "none"; return nlohmann::json{ @@ -193,7 +203,7 @@ public: } private: - void rebuild() { + void rebuild(Gallery_Frame_Mode mode) { std::vector points{ {{-0.55F, -0.25F, 0.0F}, {255, 80, 80, 255}, 24.0F}, {{0.0F, 0.45F, 0.0F}, {80, 255, 120, 255}, 28.0F}, @@ -204,11 +214,10 @@ private: state.viewport = extent_; state.visual_family = visual_family(case_id_); scene_ = Render_Scene_3D::Builder{state}.build( - visual_, make_strategy(mode_)); + visual_, make_strategy(mode)); } std::string case_id_; - Gallery_Frame_Mode mode_; Extent extent_{560, 320}; std::shared_ptr visual_; std::unique_ptr scene_; diff --git a/web_server/tests/Web_Bridge_Tests.cpp b/web_server/tests/Web_Bridge_Tests.cpp index 6bde7b2..24f2399 100644 --- a/web_server/tests/Web_Bridge_Tests.cpp +++ b/web_server/tests/Web_Bridge_Tests.cpp @@ -61,7 +61,13 @@ TEST(RenderiveWebGallery, CatalogUsesOneDefaultPageAndThreeDynamicStrategies) { const auto catalog = nlohmann::json::parse(Gallery_Protocol::catalog_json()); ASSERT_EQ(catalog.at("type"), "catalog"); EXPECT_EQ(catalog.at("cases").size(), 23U); + EXPECT_EQ(catalog.at("protocol_version"), 6); EXPECT_EQ(catalog.at("frame_modes").size(), 3U); + for (const auto& mode : catalog.at("frame_modes")) { + EXPECT_FALSE(mode.contains("automatic")); + EXPECT_FALSE(mode.contains("request_after_response")); + EXPECT_FALSE(mode.contains("request_on_animation_frame")); + } EXPECT_EQ(catalog.at("coverage").at("page_count"), 1U); EXPECT_EQ(catalog.at("coverage").at("canvas_count"), 23U); diff --git a/webapp_gallery/src/protocol/gallery_types.ts b/webapp_gallery/src/protocol/gallery_types.ts index ca2861e..705861c 100644 --- a/webapp_gallery/src/protocol/gallery_types.ts +++ b/webapp_gallery/src/protocol/gallery_types.ts @@ -15,7 +15,7 @@ export interface Gallery_Action {id: string; label: string; api: string; group?: export interface Gallery_Navigation {default_mode: string; all_categories_label: string; hero_eyebrow: string; hero_title: string; catalog_loaded_text: string;} export interface Gallery_Coverage {case_count: number; page_count: number; canvas_count: number; manual_control_count: number; manual_action_count: number;} -export interface Gallery_Frame_Mode {id: string; title: string; strategy: string; description: string; order: number; accent: string; automatic: boolean; request_after_response: boolean; request_on_animation_frame: boolean; observer_visible: boolean; frame_button_label: string;} +export interface Gallery_Frame_Mode {id: string; title: string; strategy: string; description: string; order: number; accent: string; observer_visible: boolean; frame_button_label: string;} export interface Gallery_Case {id: string; title: string; description: string; component: string; category: string; order: number; control_count_by_mode?: Record; action_count_by_mode?: Record;} export interface Gallery_Dashboard_Field {label: string; source?: string; sources?: string[]; enabled_source?: string; duration_sources?: Record; duration_source?: string; active_value?: Json_Primitive; disabled_label?: string; default?: Json_Primitive; digits?: number; format?: string; separator?: string; joiner?: string; value_map?: string; cell_class?: string; trend_group?: string; summary?: boolean;} export interface Gallery_Dashboard_Observer_Section {class_name: string; aria_label?: string; fields: Gallery_Dashboard_Field[];} @@ -28,7 +28,7 @@ export interface Gallery_Performance extends Record {} export interface Gallery_Kernel_Observer extends Record {} export interface Gallery_Scheduler_Statistics {concurrency: number; active_workers: number; peak_workers: number; active_external_threads: number; peak_external_threads: number; worker_entry_count: number; worker_exit_count: number; external_entry_count: number; external_exit_count: number;} export interface Gallery_Renderable_Observer extends Gallery_Resource {} -export interface Gallery_Telemetry {[key: string]: unknown; scheduler?: Gallery_Scheduler_Statistics; queue_pressure?: Record; renderable_observers?: Gallery_Renderable_Observer[]; performance_capture?: Gallery_Performance_Capture;} +export interface Gallery_Telemetry {[key: string]: unknown; kernel_observer?: Gallery_Kernel_Observer; scheduler?: Gallery_Scheduler_Statistics; queue_pressure?: Record; renderable_observers?: Gallery_Renderable_Observer[]; performance_capture?: Gallery_Performance_Capture;} export interface Gallery_Render_Node {node_id: number; id?: string | number; name: string; label?: string; owner: string; kind: string;} export interface Gallery_Render_Edge {from: number | string; to: number | string;} @@ -46,6 +46,6 @@ export interface Gallery_Capture_Session {session_id: number; active: boolean; r export interface Gallery_Performance_Capture {controller: {enabled?: boolean; session_id?: number}; sessions: Gallery_Capture_Session[]; plans: Gallery_Render_Plan[];} export interface Gallery_Error_Response {type: "error"; message: string; field_errors?: Record;} -export interface Gallery_Case_State {type: "case_state" | "refresh_state"; case?: string; frame_mode?: {strategy?: string} | string; controls?: {resources?: Gallery_Resource[]; observers?: Gallery_Renderable_Observer[]; render_plan?: Gallery_Render_Plan; performance_capture?: Gallery_Performance_Capture}; actions?: {data?: Gallery_Action[]}; telemetry?: Gallery_Telemetry; notice?: string;} -export interface Gallery_Observer_State {type: "observer_state"; telemetry: Gallery_Telemetry;} +export interface Gallery_Case_State {type: "case_state" | "refresh_state"; case?: string; frame_mode?: string; controls?: {resources?: Gallery_Resource[]; observers?: Gallery_Renderable_Observer[]; render_plan?: Gallery_Render_Plan; performance_capture?: Gallery_Performance_Capture}; actions?: {data?: Gallery_Action[]}; telemetry?: Gallery_Telemetry; notice?: string;} +export interface Gallery_Observer_State {type: "observer_state"; frame_mode?: string; telemetry: Gallery_Telemetry;} export type Gallery_Response = Gallery_Catalog | Gallery_Error_Response | Gallery_Case_State | Gallery_Observer_State; diff --git a/webapp_gallery/src/runtime/pixel_presenter.ts b/webapp_gallery/src/runtime/pixel_presenter.ts index 7aa5b75..cba65d5 100644 --- a/webapp_gallery/src/runtime/pixel_presenter.ts +++ b/webapp_gallery/src/runtime/pixel_presenter.ts @@ -3,6 +3,7 @@ export class Pixel_Presenter { private latest_frame: Pixel_Frame | null = null; private canvas: HTMLCanvasElement | null = null; private context: CanvasRenderingContext2D | null = null; attach(canvas: HTMLCanvasElement): void {this.canvas=canvas; this.context=canvas.getContext("2d",{alpha:false});} detach(): void {this.canvas=null;this.context=null;this.latest_frame=null;} + has_pending_frame(): boolean {return this.latest_frame!==null;} accept(buffer: ArrayBuffer): {sequence: bigint; overwritten: boolean} {const frame=decode_pixel_frame(buffer);const overwritten=this.latest_frame!==null;this.latest_frame=frame;return {sequence:frame.sequence,overwritten};} present(): boolean {const frame=this.latest_frame, canvas=this.canvas, context=this.context;if(!frame||!canvas||!context)return false;this.latest_frame=null;if(canvas.width!==frame.width||canvas.height!==frame.height){canvas.width=frame.width;canvas.height=frame.height;}const pixels=new Uint8ClampedArray(pack_pixel_rows(frame));context.putImageData(new ImageData(pixels,frame.width,frame.height),0,0);return true;} } diff --git a/webapp_gallery/src/session/gallery_plot_session.ts b/webapp_gallery/src/session/gallery_plot_session.ts index fceba70..20b26b8 100644 --- a/webapp_gallery/src/session/gallery_plot_session.ts +++ b/webapp_gallery/src/session/gallery_plot_session.ts @@ -15,7 +15,7 @@ export class Gallery_Plot_Session { private readonly client_performance = new Client_Performance(); private readonly frame_controller: Frame_Request_Controller; private current_frame_mode: Gallery_Frame_Mode; - private disposed = false; private visible = false; private streams_paused = false; private last_observe_at = 0; private sent_width = 0; private sent_height = 0; private received_frames = 0; + private disposed = false; private visible = false; private streams_paused = false; private last_frame_request_at = 0; private last_observe_at = 0; private sent_width = 0; private sent_height = 0; private received_frames = 0; constructor(readonly gallery_case: Gallery_Case, frame_mode: Gallery_Frame_Mode, private readonly frame_modes: Gallery_Frame_Mode[]) { this.current_frame_mode=frame_mode; @@ -26,17 +26,19 @@ export class Gallery_Plot_Session { subscribe = (listener: () => void): (() => void) => {this.listeners.add(listener);return()=>this.listeners.delete(listener);}; get_snapshot = (): Gallery_Plot_Snapshot => this.snapshot; private update(patch: Partial): void {this.snapshot={...this.snapshot,...patch};this.listeners.forEach(listener=>listener());} + private update_frame_mode(mode_id: string | undefined): void {const mode=this.frame_modes.find(value=>value.id===mode_id);if(!mode||mode.id===this.current_frame_mode.id)return;this.current_frame_mode=mode;this.last_frame_request_at=0;} + private next_refresh_interval_ms(): number {const value=this.snapshot.telemetry.kernel_observer?.next_refresh_interval_ns;return typeof value==="number"&&Number.isFinite(value)&&value>0?value/1_000_000:0;} connect(): void {if(this.disposed)return;this.socket.connect();} dispose(): void {this.disposed=true;this.frame_controller.cancel();if(this.snapshot.ready)this.socket.send("hide");this.socket.close();this.presenter.detach();this.listeners.clear();} attach_canvas(canvas: HTMLCanvasElement): void {this.presenter.attach(canvas);} detach_canvas(): void {this.presenter.detach();} - set_activity(visible: boolean, streams_paused: boolean): void {const was_active=this.stream_active();this.visible=visible;this.streams_paused=streams_paused;if(visible&&!this.socket.is_open())this.connect();const active=this.stream_active();if(this.snapshot.ready&&active!==was_active)this.socket.send(active?"show":"hide");if(active)this.request_frame(performance.now());} + set_activity(visible: boolean, streams_paused: boolean): void {const was_active=this.stream_active();this.visible=visible;this.streams_paused=streams_paused;if(visible&&!this.socket.is_open())this.connect();const active=this.stream_active();if(active&&!was_active)this.last_frame_request_at=0;if(this.snapshot.ready&&active!==was_active)this.socket.send(active?"show":"hide");if(active)this.request_frame(performance.now());} private stream_active(): boolean {return this.visible&&!this.streams_paused;} - private receive_socket_state(state: "connecting"|"ready"|"error"|"closed"): void {if(this.disposed)return;if(state==="ready"){this.update({connection_state:"ready",protocol_error:""});this.socket.send("gallery_open",{case:this.gallery_case.id,frame_mode:this.frame_mode.id});return;}this.update({connection_state:state,ready:state==="error"?this.snapshot.ready:false});if(state==="closed"&&!this.visible)this.socket.close();} - 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_socket_state(state: "connecting"|"ready"|"error"|"closed"): void {if(this.disposed)return;if(state==="ready"){this.last_frame_request_at=0;this.update({connection_state:"ready",protocol_error:""});this.socket.send("gallery_open",{case:this.gallery_case.id,frame_mode:this.frame_mode.id});return;}this.update({connection_state:state,ready:state==="error"?this.snapshot.ready:false});if(state==="closed"&&!this.visible)this.socket.close();} + 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_frame_mode(response.frame_mode);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;this.update_frame_mode(response.frame_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||this.frame_mode.id!=="manual"))||(!explicit&&!this.frame_mode.automatic))return false;return this.frame_controller.request_frame(now);} + tick(now: number): void {if(!this.stream_active())return;this.client_performance.record_animation_frame(now);const presented=this.presenter.present();if(presented)this.client_performance.record_presentation(now);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||this.frame_mode.id!=="manual"))||(!explicit&&(this.frame_mode.id==="manual"||this.presenter.has_pending_frame())))return false;if(!explicit&&this.last_frame_request_at&&now-this.last_frame_request_at): void {this.socket.send(type,payload);} leave(): void {this.socket.send("leave");}