修复webserver死锁
This commit is contained in:
@@ -11,3 +11,4 @@
|
||||
/webapp_gallery/.playwright-cli/
|
||||
/logs/
|
||||
/deadlock_capture/
|
||||
/*.zip
|
||||
|
||||
@@ -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
|
||||
@@ -1,30 +1,19 @@
|
||||
#include <renderive/error/Error_Policy.hpp>
|
||||
#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 <renderive/scheduling/Scheduler.hpp>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
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<Gallery_Scene_Interface> make_gallery_scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) {
|
||||
std::unique_ptr<Gallery_Scene_Interface> 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<void>(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<void()> 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<void()> 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<std::function<void()>> tasks_;
|
||||
std::vector<std::jthread> workers_;
|
||||
};
|
||||
Automatic_Render_Executor& automatic_render_executor() {
|
||||
static Automatic_Render_Executor executor;
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Session::Impl> {
|
||||
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<std::uint64_t> 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<Gallery_Plot_Se
|
||||
[[nodiscard]] std::unique_lock<std::mutex> acquire_foreground_lock() {
|
||||
return std::unique_lock<std::mutex>(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<std::uint64_t>(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<double>(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<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, Frame_Request>) {
|
||||
return false;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Gallery_Request>) {
|
||||
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<Gallery_Scene_Interface> scene;
|
||||
std::mutex mutex;
|
||||
std::optional<Clock::time_point> 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<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()};
|
||||
@@ -372,10 +201,7 @@ struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Se
|
||||
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();
|
||||
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<Gallery_Plot_Se
|
||||
Gallery_Protocol::error_json("请先发送 gallery_open")
|
||||
};
|
||||
if (request.kind == Gallery_Request_Kind::Observe) {
|
||||
if (update_client_metrics(request.message))
|
||||
arm_automatic_render_locked();
|
||||
static_cast<void>(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<Gallery_Plot_Se
|
||||
};
|
||||
}
|
||||
if (request.kind == Gallery_Request_Kind::Refresh) {
|
||||
if (update_client_metrics(request.message))
|
||||
arm_automatic_render_locked();
|
||||
static_cast<void>(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_this<Gallery_Plot_Se
|
||||
Web_Response_Type::Json,
|
||||
Gallery_Protocol::error_json("gallery_action 格式无效")
|
||||
};
|
||||
if (!Gallery_Protocol::action_available(scene->case_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_this<Gallery_Plot_Se
|
||||
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();
|
||||
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<Gallery_Plot_Se
|
||||
std::optional<std::string> 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<Gallery_Plot_Se
|
||||
return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)};
|
||||
}
|
||||
std::optional<Web_Response> 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<Frame_Request>(event))
|
||||
return handle_frame_request();
|
||||
const bool reschedule = affects_render_schedule(event);
|
||||
std::optional<Web_Response> response;
|
||||
{
|
||||
auto lock = acquire_foreground_lock();
|
||||
response = std::visit(
|
||||
[this](const auto& value) -> std::optional<Web_Response> {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, Gallery_Request>) {
|
||||
return handle_gallery(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Frame_Request>) {
|
||||
return std::nullopt;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Viewport_Resize>) {
|
||||
if (scene)
|
||||
scene->resize(value.size.width, value.size.height);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Event>) {
|
||||
if (scene)
|
||||
scene->dispatch(Gallery_Input_Event{gallery_input(value)});
|
||||
}
|
||||
else if constexpr (Event_Object<T>) {
|
||||
if (scene)
|
||||
scene->dispatch(Gallery_Input_Event{gallery_input(value)});
|
||||
}
|
||||
auto lock = acquire_foreground_lock();
|
||||
return std::visit(
|
||||
[this](const auto& value) -> std::optional<Web_Response> {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, Gallery_Request>) {
|
||||
return handle_gallery(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Frame_Request>) {
|
||||
return std::nullopt;
|
||||
},
|
||||
event);
|
||||
if (reschedule)
|
||||
arm_automatic_render_locked();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Viewport_Resize>) {
|
||||
if (scene)
|
||||
scene->resize(value.size.width, value.size.height);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Event>) {
|
||||
if (scene)
|
||||
scene->dispatch(Gallery_Input_Event{gallery_input(value)});
|
||||
}
|
||||
else if constexpr (Event_Object<T>) {
|
||||
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<Impl>(automatic_low_latency)) {}
|
||||
Gallery_Plot_Session::Gallery_Plot_Session() : impl_(std::make_shared<Impl>()) {}
|
||||
Gallery_Plot_Session::~Gallery_Plot_Session() = default;
|
||||
std::optional<Web_Response> Gallery_Plot_Session::handle(const Web_Event& event) {
|
||||
return impl_->handle(event);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <class T>
|
||||
Gallery_Request_Result<T> 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
|
||||
|
||||
@@ -51,6 +51,6 @@ public:
|
||||
[[nodiscard]] static Gallery_Request_Result<Gallery_Action_Request> action_request(std::string_view message);
|
||||
[[nodiscard]] static Gallery_Request_Result<Gallery_Control_Patch_Request> 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
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace renderive::web {
|
||||
void Gallery_WebSocket_Controller::handleNewConnection(
|
||||
const drogon::HttpRequestPtr&,
|
||||
const drogon::WebSocketConnectionPtr& connection) {
|
||||
connection->setContext(std::make_shared<Gallery_Plot_Session>(true));
|
||||
connection->setContext(std::make_shared<Gallery_Plot_Session>());
|
||||
connection->setPingMessage("renderive-gallery", std::chrono::seconds(20));
|
||||
LOG_INFO << "Renderive Gallery WebSocket connected: "
|
||||
<< connection->peerAddr().toIpPort();
|
||||
|
||||
@@ -6,20 +6,10 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
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<std::string> 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;
|
||||
|
||||
@@ -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<std::string> 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<void>(render_latest_frame());
|
||||
return "frame rendered";
|
||||
}
|
||||
if (request.id == "capture_next_frame") {
|
||||
const auto capture = scene_->capture_next_frame();
|
||||
if (capture) static_cast<void>(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::size_t>(std::clamp(
|
||||
std::get<double>(*request.argument), 1.0, 1000.0));
|
||||
const auto capture = scene_->capture_frames(count);
|
||||
if (capture) static_cast<void>(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<void>(scene_->render_frame(true));
|
||||
static_cast<void>(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<Render_Scene_2D> scene_;
|
||||
renderive_Owner<Frequency_Axis> frequency_axis_;
|
||||
@@ -458,9 +436,7 @@ private:
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene_2d(
|
||||
std::uint64_t, std::string case_id, Gallery_Frame_Mode mode,
|
||||
bool automatic_low_latency) {
|
||||
return std::make_unique<Gallery_Scene2D>(
|
||||
std::move(case_id), mode, automatic_low_latency);
|
||||
std::uint64_t, std::string case_id, Gallery_Frame_Mode mode) {
|
||||
return std::make_unique<Gallery_Scene2D>(std::move(case_id), mode);
|
||||
}
|
||||
} // namespace renderive::web
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
namespace renderive::web {
|
||||
[[nodiscard]] std::unique_ptr<Gallery_Scene_Interface> 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<Gallery_Scene_Interface> make_gallery_scene_2d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode);
|
||||
}
|
||||
|
||||
@@ -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<std::string> 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<void>(render_latest_frame());
|
||||
return "frame rendered";
|
||||
}
|
||||
if (request.id == "capture_next_frame") {
|
||||
const auto capture = scene_->capture_next_frame();
|
||||
if (capture) static_cast<void>(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::size_t>(std::clamp(
|
||||
std::get<double>(*request.argument), 1.0, 1000.0));
|
||||
const auto capture = scene_->capture_frames(count);
|
||||
if (capture) static_cast<void>(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<void>(scene_->request_frame());
|
||||
}
|
||||
|
||||
std::string case_id_;
|
||||
Gallery_Frame_Mode mode_;
|
||||
bool automatic_{};
|
||||
Extent extent_{560, 320};
|
||||
std::shared_ptr<Point_Visual> visual_;
|
||||
std::unique_ptr<Render_Scene_3D> scene_;
|
||||
@@ -218,9 +203,7 @@ private:
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene_3d(
|
||||
std::uint64_t, std::string case_id, Gallery_Frame_Mode mode,
|
||||
bool automatic_low_latency) {
|
||||
return std::make_unique<Gallery_Scene3D>(
|
||||
std::move(case_id), mode, automatic_low_latency);
|
||||
std::uint64_t, std::string case_id, Gallery_Frame_Mode mode) {
|
||||
return std::make_unique<Gallery_Scene3D>(std::move(case_id), mode);
|
||||
}
|
||||
} // namespace renderive::web
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
namespace renderive::web {
|
||||
[[nodiscard]] std::unique_ptr<Gallery_Scene_Interface> 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<Gallery_Scene_Interface> make_gallery_scene_3d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,6 +21,6 @@ export function Plot_Card({gallery_case,frame_mode,frame_modes,dashboard,active,
|
||||
return <Card ref={card_ref} sx={{overflow:"hidden",display:"flex",flexDirection:"column",minWidth:0}} onContextMenu={event=>{event.preventDefault();if(snapshot.ready)on_open_inspector({session});}}>
|
||||
<CardHeader title={gallery_case.title} subheader={gallery_case.component} avatar={<Chip size="small" label={gallery_case.category}/>} action={<Plot_Status snapshot={snapshot} active={active&&!streams_paused}/>}/>
|
||||
<Plot_Canvas session={session}/><Performance_Strip telemetry={snapshot.telemetry} dashboard={dashboard}/>{frame_mode.observer_visible&&<Kernel_Observer_Summary telemetry={snapshot.telemetry} dashboard={dashboard}/>}<CardContent sx={{flexGrow:1}}><Typography color="text.secondary">{gallery_case.description}</Typography>{snapshot.notice&&<Typography variant="caption" color="secondary.main">{snapshot.notice}</Typography>}</CardContent>
|
||||
<CardActions sx={{justifyContent:"space-between",px:2,pb:2}}><Stack direction="row" spacing={2}><Typography variant="caption">{snapshot.controls.length} 属性</Typography><Typography variant="caption">{snapshot.actions.length} 动作</Typography><Typography variant="caption">{snapshot.frame_count} 像素帧</Typography></Stack><Box><Button size="small" startIcon={<RefreshIcon/>} onClick={()=>session.request_frame(performance.now(),true)}>{frame_mode.frame_button_label}</Button><Button size="small" startIcon={<MoreHorizIcon/>} disabled={!snapshot.ready} onClick={()=>on_open_inspector({session})}>测试菜单</Button></Box></CardActions>
|
||||
<CardActions sx={{justifyContent:"space-between",px:2,pb:2}}><Stack direction="row" spacing={2}><Typography variant="caption">{snapshot.controls.length} 属性</Typography><Typography variant="caption">{snapshot.actions.length} 动作</Typography><Typography variant="caption">{snapshot.frame_count} 像素帧</Typography></Stack><Box>{session.frame_mode.id==="manual"&&<Button size="small" startIcon={<RefreshIcon/>} onClick={()=>session.request_frame(performance.now(),true)}>{session.frame_mode.frame_button_label}</Button>}<Button size="small" startIcon={<MoreHorizIcon/>} disabled={!snapshot.ready} onClick={()=>on_open_inspector({session})}>测试菜单</Button></Box></CardActions>
|
||||
</Card>;
|
||||
}
|
||||
|
||||
@@ -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<string, Json_Value>): void {this.socket.send(type,payload);}
|
||||
leave(): void {this.socket.send("leave");}
|
||||
|
||||
Reference in New Issue
Block a user