Files
Aethera/web_server/src/Graph_WebSocket.cpp
T
2026-08-21 15:12:22 +08:00

52 lines
4.2 KiB
C++

#include "Graph_WebSocket.hpp" /* 图像素流 WebSocket。 */
#include <nlohmann/json.hpp>
#include <algorithm>
#include <chrono>
#include <string>
#include <unordered_map>
#include <mutex>
namespace aethera::web {
namespace {
std::string graph_id_from_path(std::string_view path) { const auto split = path.find_last_of('/'); return split == std::string_view::npos ? std::string{} : std::string(path.substr(split + 1)); }
}
struct Graph_WebSocket::Private {
std::weak_ptr<drogon::WebSocketConnection> connection; /* 不延长已关闭 Drogon 连接生命周期。 */
std::shared_ptr<Plot> plot; /* 连接期间保持引擎桥接对象存活。 */
const void* owner{}; /* 会话订阅表使用的稳定连接身份。 */
bool attached{}; /* 是否已安装像素订阅。 */
};
Graph_WebSocket::Graph_WebSocket(drogon::WebSocketConnectionPtr connection, std::shared_ptr<Plot> plot) : d(std::make_unique<Private>()) { d->connection = connection; d->plot = std::move(plot); d->owner = connection.get(); }
Graph_WebSocket::~Graph_WebSocket() { close(); }
void Graph_WebSocket::start() { if (d->attached) return; const auto weak = weak_from_this(); d->plot->attach(d->owner, [weak](std::string pixels) { const auto socket = weak.lock(); if (!socket) return; const auto connection = socket->d->connection.lock(); if (connection && connection->connected()) connection->send(pixels.data(), pixels.size(), drogon::WebSocketMessageType::Binary); }); d->attached = true; }
void Graph_WebSocket::receive(std::string_view message) {
const auto json = nlohmann::json::parse(message, nullptr, false);
if (json.is_discarded() || !json.is_object()) return;
Plot_Event event;
if (const auto value = json.find("time"); value != json.end() && value->is_number())
event.time_milliseconds = value->get<double>();
if (const auto value = json.find("width"); value != json.end() && value->is_number_unsigned())
event.width = std::clamp(value->get<std::uint32_t>(), 160U, 1920U);
if (const auto value = json.find("height"); value != json.end() && value->is_number_unsigned())
event.height = std::clamp(value->get<std::uint32_t>(), 120U, 1080U);
if (const auto value = json.find("wheel"); value != json.end() && value->is_object()) {
const auto x = value->find("x");
const auto y = value->find("y");
const auto delta_y = value->find("delta_y");
if (x != value->end() && x->is_number() && y != value->end() && y->is_number()
&& delta_y != value->end() && delta_y->is_number()) {
event.wheel = Plot_Wheel_Event{
std::clamp(x->get<double>(), 0.0, static_cast<double>(event.width)),
std::clamp(y->get<double>(), 0.0, static_cast<double>(event.height)),
std::clamp(delta_y->get<double>(), -120.0, 120.0)};
}
}
d->plot->submit(event);
}
void Graph_WebSocket::close() { if (!d->attached) return; d->plot->detach(d->owner); d->attached = false; }
Graph_WebSocket_Controller::Graph_WebSocket_Controller(Plot_Resolver resolver) : resolve_plot(std::move(resolver)) {}
void Graph_WebSocket_Controller::handleNewConnection(const drogon::HttpRequestPtr& request, const drogon::WebSocketConnectionPtr& connection) { auto plot = resolve_plot(graph_id_from_path(request->path())); if (!plot) { connection->shutdown(drogon::CloseCode::kViolation, "Unknown Aethera plot"); return; } auto socket = std::make_shared<Graph_WebSocket>(connection, std::move(plot)); connection->setContext(socket); connection->setPingMessage("aethera-gallery", std::chrono::seconds(20)); socket->start(); }
void Graph_WebSocket_Controller::handleNewMessage(const drogon::WebSocketConnectionPtr& connection, std::string&& message, const drogon::WebSocketMessageType& type) { if (type != drogon::WebSocketMessageType::Text || message.size() > 64 * 1024) return; if (const auto socket = connection->getContext<Graph_WebSocket>()) socket->receive(message); }
void Graph_WebSocket_Controller::handleConnectionClosed(const drogon::WebSocketConnectionPtr& connection) { if (const auto socket = connection->getContext<Graph_WebSocket>()) socket->close(); connection->clearContext(); }
}