#include "Graph_WebSocket.hpp" /* 图像素流 WebSocket。 */ #include #include #include #include #include #include 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 connection; /* 不延长已关闭 Drogon 连接生命周期。 */ std::shared_ptr plot; /* 连接期间保持引擎桥接对象存活。 */ const void* owner{}; /* 会话订阅表使用的稳定连接身份。 */ bool attached{}; /* 是否已安装像素订阅。 */ }; Graph_WebSocket::Graph_WebSocket(drogon::WebSocketConnectionPtr connection, std::shared_ptr plot) : d(std::make_unique()) { 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(); if (const auto value = json.find("width"); value != json.end() && value->is_number_unsigned()) event.width = std::clamp(value->get(), 160U, 1920U); if (const auto value = json.find("height"); value != json.end() && value->is_number_unsigned()) event.height = std::clamp(value->get(), 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(), 0.0, static_cast(event.width)), std::clamp(y->get(), 0.0, static_cast(event.height)), std::clamp(delta_y->get(), -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(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()) socket->receive(message); } void Graph_WebSocket_Controller::handleConnectionClosed(const drogon::WebSocketConnectionPtr& connection) { if (const auto socket = connection->getContext()) socket->close(); connection->clearContext(); } }