优化
This commit is contained in:
@@ -309,9 +309,10 @@ nlohmann::json generate_2d_data(Object& object, const Json& input) {
|
||||
std::ranges::copy_n(complete.begin() + block * width, width, blocks[block].begin());
|
||||
object.template set<&Sweep_Spectrum::Prop::bins_per_block>(width);
|
||||
object.template set<&Sweep_Spectrum::Prop::block_count>(block_count);
|
||||
for (auto& block : blocks)
|
||||
for (std::size_t block_index = 0; block_index < blocks.size(); ++block_index)
|
||||
object.template submit_stream<Sweep_Spectrum_Stream_Tag>(
|
||||
std::make_shared<const std::vector<Plot_Value>>(std::move(block)));
|
||||
std::make_shared<const Sweep_Spectrum_Block>(
|
||||
Sweep_Spectrum_Block{block_index, std::move(blocks[block_index])}));
|
||||
object.template mark_dirty<Prepare_Data_Tag>();
|
||||
generated_count = block_count * width;
|
||||
} else if constexpr (std::same_as<Definition, Afterglow>) {
|
||||
@@ -675,7 +676,8 @@ std::shared_ptr<Plot> make_sweep_spectrum_plot(asio::any_io_executor executor) {
|
||||
values[i] = -90.0 + 35.0 * std::sin(sweep_index * 0.08 + event.time_milliseconds * 0.002);
|
||||
}
|
||||
raw->template submit_stream<Sweep_Spectrum_Stream_Tag>(
|
||||
std::make_shared<const std::vector<Plot_Value>>(std::move(values)));
|
||||
std::make_shared<const Sweep_Spectrum_Block>(
|
||||
Sweep_Spectrum_Block{block_index, std::move(values)}));
|
||||
raw->template mark_dirty<Prepare_Data_Tag>();
|
||||
};
|
||||
auto view = make_scene_view<
|
||||
|
||||
@@ -69,25 +69,6 @@ void Graph_WebSocket::deliver_frame(
|
||||
}
|
||||
}
|
||||
|
||||
void Graph_WebSocket::deliver_input_completion(
|
||||
Plot_Input_Observation observation) {
|
||||
if (!d->attached.load(std::memory_order_acquire)) return;
|
||||
const auto connection = d->connection.lock();
|
||||
if (!connection || !connection->connected()) return;
|
||||
const nlohmann::json message{
|
||||
{"kind", "input_completion"},
|
||||
{"protocol", "aethera.input.latency"},
|
||||
{"version", 2},
|
||||
{"sequence", observation.sequence},
|
||||
{"rendered_frame_sequence", observation.rendered_frame_sequence},
|
||||
{"coalesced_event_count", observation.coalesced_event_count}
|
||||
};
|
||||
try {
|
||||
connection->send(message.dump(), drogon::WebSocketMessageType::Text);
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
void Graph_WebSocket::receive(std::string_view message) {
|
||||
const auto received_time = std::chrono::steady_clock::now();
|
||||
const auto json = nlohmann::json::parse(message, nullptr, false);
|
||||
@@ -126,8 +107,6 @@ void Graph_WebSocket::receive(std::string_view message) {
|
||||
height = d->height;
|
||||
}
|
||||
Plot_Input_Event decoded;
|
||||
decoded.sequence = input->value("sequence", std::uint64_t{});
|
||||
if (decoded.sequence == 0) return;
|
||||
decoded.coalesced_event_count = std::clamp(
|
||||
input->value("coalesced_event_count", std::size_t{1}),
|
||||
std::size_t{1}, std::size_t{1'000});
|
||||
@@ -167,13 +146,7 @@ void Graph_WebSocket::receive(std::string_view message) {
|
||||
input->value("key", std::string{"unknown"})).value_or(Key::unknown);
|
||||
decoded.native_key = input->value("native_key", 0U);
|
||||
decoded.auto_repeat = input->value("auto_repeat", false);
|
||||
const auto weak = weak_from_this();
|
||||
d->plot->submit_input(
|
||||
std::move(decoded),
|
||||
[weak](Plot_Input_Observation observation) {
|
||||
if (const auto socket = weak.lock())
|
||||
socket->deliver_input_completion(std::move(observation));
|
||||
});
|
||||
d->plot->submit_input(std::move(decoded));
|
||||
}
|
||||
catch (const nlohmann::json::exception&) {}
|
||||
catch (...) {}
|
||||
|
||||
@@ -19,7 +19,6 @@ public:
|
||||
void close() noexcept;
|
||||
private:
|
||||
void deliver_frame(std::shared_ptr<const Plot_Stream_Frame> frame);
|
||||
void deliver_input_completion(Plot_Input_Observation observation);
|
||||
struct Private;
|
||||
std::unique_ptr<Private> d;
|
||||
};
|
||||
|
||||
+30
-51
@@ -62,9 +62,15 @@ double elapsed_milliseconds(std::chrono::steady_clock::time_point start,
|
||||
|
||||
struct Web_Input_Metadata {
|
||||
Plot_Input_Event input;
|
||||
Plot::Input_Handler handler;
|
||||
std::chrono::steady_clock::time_point scene_dispatched;
|
||||
};
|
||||
struct Plot_Input_Observation {
|
||||
double admission_ms{}; /* WebSocket 接收到提交 Scene 事件流的耗时。 */
|
||||
double scene_wait_ms{}; /* Scene 收到事件到 Prepare 消费事件的等待。 */
|
||||
double dispatch_ms{}; /* Scene 开始分发到 Renderable 完成消费的耗时。 */
|
||||
double server_consume_ms{}; /* WebSocket 接收到 Renderable 完成消费的服务端总耗时。 */
|
||||
std::size_t coalesced_event_count{}; /* 本次服务端事件代表的浏览器原始事件数量。 */
|
||||
};
|
||||
struct Web_Input_Event {
|
||||
virtual ~Web_Input_Event() = default;
|
||||
[[nodiscard]] virtual const Web_Input_Metadata& web_input_metadata() const noexcept = 0;
|
||||
@@ -413,12 +419,11 @@ void Plot_Diagnostics::submit(
|
||||
}
|
||||
|
||||
void Plot_Diagnostics::submit_input(const Plot_Input_Observation& observation) {
|
||||
const std::array<std::pair<std::string_view, double>, 6> values{{
|
||||
{"input_plot_queue_ms", observation.plot_queue_ms},
|
||||
{"input_scene_queue_ms", observation.scene_queue_ms},
|
||||
{"input_backend_queue_ms", observation.backend_queue_ms},
|
||||
{"input_frame_completion_ms", observation.frame_completion_ms},
|
||||
{"input_server_total_ms", observation.server_total_ms},
|
||||
const std::array<std::pair<std::string_view, double>, 5> values{{
|
||||
{"input_admission_ms", observation.admission_ms},
|
||||
{"input_scene_wait_ms", observation.scene_wait_ms},
|
||||
{"input_dispatch_ms", observation.dispatch_ms},
|
||||
{"input_server_consume_ms", observation.server_consume_ms},
|
||||
{"input_coalesced_event_count", static_cast<double>(observation.coalesced_event_count)}}};
|
||||
input_values.submit(values);
|
||||
}
|
||||
@@ -466,10 +471,8 @@ nlohmann::json Plot_Diagnostics::snapshot() const {
|
||||
}
|
||||
|
||||
template <typename Scene_Object>
|
||||
void dispatch_plot_input(Scene_Object& scene, const Plot_Input_Event& input,
|
||||
Plot::Input_Handler handler) {
|
||||
Web_Input_Metadata metadata{input, std::move(handler),
|
||||
std::chrono::steady_clock::now()};
|
||||
void dispatch_plot_input(Scene_Object& scene, const Plot_Input_Event& input) {
|
||||
Web_Input_Metadata metadata{input, std::chrono::steady_clock::now()};
|
||||
const auto dispatch = [&](auto event) {
|
||||
scene.template submit_stream<aethera::Scene_Event_Stream_Tag>(std::move(event));
|
||||
};
|
||||
@@ -568,7 +571,6 @@ struct Plot::Private {
|
||||
bool tick_task_scheduled{}; /* Plot strand 上是否已有唯一消费任务。 */
|
||||
double last_clock_render_time_ms{-std::numeric_limits<double>::infinity()};
|
||||
std::chrono::steady_clock::time_point clock_origin{std::chrono::steady_clock::now()};
|
||||
std::vector<aethera::Scene::Event_Report_Pointer> pending_event_reports; /* Scene 已处理、等待目标画面完成的报告。 */
|
||||
|
||||
template <typename Scene_Object>
|
||||
Private(asio::any_io_executor executor, std::unique_ptr<Scene_Object> value_scene,
|
||||
@@ -590,7 +592,7 @@ struct Plot::Private {
|
||||
void clock_tick(const Plot_Render_Tick& tick);
|
||||
void render_frame(Plot_Render_Tick tick);
|
||||
void queue_completed_frame(Render_Frame* frame);
|
||||
void complete_event_reports(Frame_Identity rendered_identity);
|
||||
void collect_consumed_input_statistics();
|
||||
void fail(std::exception_ptr failure) noexcept;
|
||||
};
|
||||
|
||||
@@ -746,6 +748,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
(*scene_2d)->set<&Render_Scene_2D::Prop::viewport>(
|
||||
Size{static_cast<int>(tick.width), static_cast<int>(tick.height)});
|
||||
const auto result = (*scene_2d)->render(&output);
|
||||
collect_consumed_input_statistics();
|
||||
if (result != Render_Scene_2D::Render_Result::completed)
|
||||
rollback_unsubmitted();
|
||||
return;
|
||||
@@ -757,6 +760,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(scene);
|
||||
scene_3d->set<&Render_Scene_3D::Prop::viewport>(Extent{tick.width, tick.height});
|
||||
const auto result = scene_3d->render(&output);
|
||||
collect_consumed_input_statistics();
|
||||
if (result == Render_Scene_3D::Render_Result::submitted) return;
|
||||
rollback_unsubmitted();
|
||||
if (result == Render_Scene_3D::Render_Result::backend_unavailable)
|
||||
@@ -768,7 +772,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
}
|
||||
}
|
||||
|
||||
void Plot::Private::complete_event_reports(Frame_Identity rendered_identity) {
|
||||
void Plot::Private::collect_consumed_input_statistics() {
|
||||
aethera::Scene::Event_Report_Batch reports = std::visit(
|
||||
[](auto& value) {
|
||||
return value->template access_query_stream<aethera::Scene_Event_Stream_Tag>(
|
||||
@@ -779,50 +783,28 @@ void Plot::Private::complete_event_reports(Frame_Identity rendered_identity) {
|
||||
return result;
|
||||
});
|
||||
}, scene);
|
||||
pending_event_reports.insert(pending_event_reports.end(),
|
||||
std::make_move_iterator(reports.begin()),
|
||||
std::make_move_iterator(reports.end()));
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto first_complete = std::stable_partition(
|
||||
pending_event_reports.begin(), pending_event_reports.end(),
|
||||
[&](const aethera::Scene::Event_Report_Pointer& event) {
|
||||
const auto timing = event->dispatch_timing();
|
||||
return timing.completed_steady_ns == 0 ||
|
||||
timing.frame_sequence > rendered_identity.sequence;
|
||||
});
|
||||
for (auto iterator = first_complete;
|
||||
iterator != pending_event_reports.end(); ++iterator) {
|
||||
const auto* web_event = dynamic_cast<const Web_Input_Event*>(iterator->get());
|
||||
for (const auto& event : reports) {
|
||||
const auto* web_event = dynamic_cast<const Web_Input_Event*>(event.get());
|
||||
if (!web_event) continue;
|
||||
const auto& metadata = web_event->web_input_metadata();
|
||||
const auto timing = (*iterator)->dispatch_timing();
|
||||
const auto timing = event->dispatch_timing();
|
||||
if (timing.completed_steady_ns == 0) continue;
|
||||
const auto started = std::chrono::steady_clock::time_point{
|
||||
std::chrono::nanoseconds(timing.started_steady_ns)};
|
||||
const auto completed = std::chrono::steady_clock::time_point{
|
||||
std::chrono::nanoseconds(timing.completed_steady_ns)};
|
||||
Plot_Input_Observation observation{};
|
||||
observation.sequence = metadata.input.sequence;
|
||||
observation.type = metadata.input.type;
|
||||
observation.stage = Plot_Input_Stage::frame_completed;
|
||||
observation.plot_queue_ms = elapsed_milliseconds(
|
||||
observation.admission_ms = elapsed_milliseconds(
|
||||
metadata.input.received_time, metadata.scene_dispatched);
|
||||
observation.scene_queue_ms = elapsed_milliseconds(
|
||||
observation.scene_wait_ms = elapsed_milliseconds(
|
||||
metadata.scene_dispatched, started);
|
||||
observation.backend_queue_ms = elapsed_milliseconds(started, completed);
|
||||
observation.frame_completion_ms = elapsed_milliseconds(completed, now);
|
||||
observation.server_total_ms = elapsed_milliseconds(
|
||||
metadata.input.received_time, now);
|
||||
observation.target_frame_sequence = timing.frame_sequence;
|
||||
observation.rendered_frame_sequence = rendered_identity.sequence;
|
||||
observation.dispatch_ms = elapsed_milliseconds(started, completed);
|
||||
observation.server_consume_ms = elapsed_milliseconds(
|
||||
metadata.input.received_time, completed);
|
||||
observation.coalesced_event_count =
|
||||
metadata.input.coalesced_event_count;
|
||||
diagnostics.submit_input(observation);
|
||||
try {
|
||||
if (metadata.handler) metadata.handler(std::move(observation));
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
pending_event_reports.erase(first_complete, pending_event_reports.end());
|
||||
}
|
||||
|
||||
void Plot::Private::queue_completed_frame(Render_Frame* frame) {
|
||||
@@ -906,8 +888,6 @@ void Plot::Private::queue_completed_frame(Render_Frame* frame) {
|
||||
pixel_storage = frame_3d->share_pixels();
|
||||
}
|
||||
|
||||
complete_event_reports(rendered_identity);
|
||||
|
||||
auto pixels = std::make_shared<const Plot_Pixel_Frame>(Plot_Pixel_Frame{
|
||||
std::move(pixel_storage), managed->presentation_time,
|
||||
identity.sequence, identity.correlation_id,
|
||||
@@ -1066,7 +1046,7 @@ void Plot::render_once() {
|
||||
});
|
||||
}
|
||||
|
||||
void Plot::submit_input(Plot_Input_Event event, Input_Handler handler) {
|
||||
void Plot::submit_input(Plot_Input_Event event) {
|
||||
ensure_started();
|
||||
if (d->terminal_failure.load(std::memory_order_acquire)) return;
|
||||
if (event.received_time == std::chrono::steady_clock::time_point{})
|
||||
@@ -1078,10 +1058,9 @@ void Plot::submit_input(Plot_Input_Event event, Input_Handler handler) {
|
||||
*/
|
||||
try {
|
||||
if (auto* scene_2d = std::get_if<std::unique_ptr<Scene_2D>>(&d->scene))
|
||||
dispatch_plot_input(**scene_2d, event, std::move(handler));
|
||||
dispatch_plot_input(**scene_2d, event);
|
||||
else
|
||||
dispatch_plot_input(*std::get<std::unique_ptr<Scene_3D>>(d->scene),
|
||||
event, std::move(handler));
|
||||
dispatch_plot_input(*std::get<std::unique_ptr<Scene_3D>>(d->scene), event);
|
||||
}
|
||||
catch (...) {
|
||||
d->fail(std::current_exception());
|
||||
|
||||
+1
-21
@@ -15,7 +15,6 @@
|
||||
namespace aethera::web {
|
||||
struct Plot_Input_Event {
|
||||
std::size_t coalesced_event_count{1}; /* 浏览器显示周期内合并的原始输入数量。 */
|
||||
std::uint64_t sequence{}; /* 页面为本次原始输入分配的单调身份。 */
|
||||
Event_Type type{Event_Type::pointer_move}; /* 输入事件业务类型。 */
|
||||
render_2d::Point_F position{}; /* Plot 像素坐标。 */
|
||||
render_2d::Point_F global_position{}; /* 浏览器屏幕像素坐标。 */
|
||||
@@ -32,24 +31,6 @@ struct Plot_Input_Event {
|
||||
std::chrono::steady_clock::time_point received_time{}; /* WebSocket 完成解析的服务端单调时刻。 */
|
||||
};
|
||||
|
||||
enum class Plot_Input_Stage : std::uint8_t {
|
||||
frame_completed
|
||||
};
|
||||
|
||||
struct Plot_Input_Observation {
|
||||
std::uint64_t sequence{}; /* 对应页面原始输入身份。 */
|
||||
Event_Type type{Event_Type::pointer_move}; /* 对应输入事件业务类型。 */
|
||||
Plot_Input_Stage stage{Plot_Input_Stage::frame_completed}; /* 对外只发布完整画面闭环。 */
|
||||
double plot_queue_ms{}; /* WebSocket 接收到 Scene 接收的等待。 */
|
||||
double scene_queue_ms{}; /* Scene 接收事件到下一次 Render 消费事件的等待。 */
|
||||
double backend_queue_ms{}; /* 事件进入渲染后端到实际应用的等待。 */
|
||||
double frame_completion_ms{}; /* 后端应用事件到包含该状态的画面完成的等待。 */
|
||||
double server_total_ms{}; /* WebSocket 接收到画面完成或当前阶段的总耗时。 */
|
||||
std::uint64_t target_frame_sequence{}; /* 首个应包含本事件状态的逻辑帧。 */
|
||||
std::uint64_t rendered_frame_sequence{}; /* 实际包含本事件状态的物理画面帧。 */
|
||||
std::size_t coalesced_event_count{}; /* 合并到本事件的连续输入数量,包含自身。 */
|
||||
};
|
||||
|
||||
struct Plot_Render_Tick {
|
||||
std::uint64_t sequence{}; /* 页面级帧时钟分配的关联序号。 */
|
||||
double time_milliseconds{}; /* 页面级单调时间线,所有图共享同一个动画时刻。 */
|
||||
@@ -78,7 +59,6 @@ public:
|
||||
using Stream_Id = std::uint64_t;
|
||||
using Stream_Handler = std::function<void(std::shared_ptr<const Plot_Stream_Frame>)>;
|
||||
using Json_Handler = std::function<void(nlohmann::json)>;
|
||||
using Input_Handler = std::function<void(Plot_Input_Observation)>;
|
||||
|
||||
class Scene_View {
|
||||
public:
|
||||
@@ -107,7 +87,7 @@ public:
|
||||
void configure_stream(Stream_Id stream, std::uint32_t width, std::uint32_t height);
|
||||
void schedule_render(Plot_Render_Tick tick);
|
||||
void render_once();
|
||||
void submit_input(Plot_Input_Event event, Input_Handler handler);
|
||||
void submit_input(Plot_Input_Event event);
|
||||
void async_schema(Json_Handler handler);
|
||||
void async_write_prop(std::string component, std::string key,
|
||||
nlohmann::json value, Json_Handler handler);
|
||||
|
||||
Reference in New Issue
Block a user