Add MainHUD gameplay UI, net handshake updates, and server maintenance mode.

Introduce UiManager/MainHUD prefab flow, strengthen GameNet/WsClient, and add AIServer maintenance endpoint support.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-09-21 17:34:42 +08:00
co-authored by Cursor
parent 10d9b98637
commit c94b8fefeb
32 changed files with 1907 additions and 74 deletions
+27 -3
View File
@@ -3,6 +3,7 @@
#include "auth.hpp"
#include "crypto.hpp"
#include "json.hpp"
#include "maintenance.hpp"
#include <algorithm>
#include <cstdio>
@@ -269,8 +270,8 @@ void ws_loop(socket_t s) {
} // namespace
HttpWsServer::HttpWsServer(db::Database& db, std::string host, uint16_t port)
: db_(db), host_(std::move(host)), port_(port) {}
HttpWsServer::HttpWsServer(db::Database& db, std::string host, uint16_t port, std::string maintenance_path)
: db_(db), host_(std::move(host)), port_(port), maintenance_path_(std::move(maintenance_path)) {}
HttpWsServer::~HttpWsServer() { stop(); }
@@ -398,6 +399,18 @@ void HttpWsServer::handle_client(uintptr_t client_sock) {
<< "\r\n";
const auto resp = oss.str();
if (!send_all(s, resp.data(), resp.size())) return;
// 维护模式:升级成功后下发 maintenance 并断开(客户端停重连)
{
const auto m = maintenance::load(maintenance_path_);
if (m.enabled) {
nlohmann::json body{{"op", "maintenance"}, {"message", m.message}, {"ok", false}};
send_ws_text(s, body.dump());
std::printf("[ws] maintenance reject pid=%lld\n", static_cast<long long>(pid));
return;
}
}
std::printf("[ws] open pid=%lld\n", static_cast<long long>(pid));
ws_loop(s);
std::printf("[ws] close pid=%lld\n", static_cast<long long>(pid));
@@ -430,7 +443,18 @@ void HttpWsServer::handle_client(uintptr_t client_sock) {
}
if (req.method == "GET" && req.path == "/health") {
auto resp = http_response(200, "OK", "{\"ok\":true,\"service\":\"aiserver\"}");
const auto m = maintenance::load(maintenance_path_);
nlohmann::json body{{"ok", true}, {"service", "aiserver"}, {"maintenance", m.enabled}};
if (m.enabled) body["message"] = m.message;
auto resp = http_response(200, "OK", body.dump());
send_all(s, resp.data(), resp.size());
return;
}
if (req.method == "GET" && req.path == "/maintenance") {
const auto m = maintenance::load(maintenance_path_);
nlohmann::json body{{"enabled", m.enabled}, {"message", m.message}};
auto resp = http_response(200, "OK", body.dump());
send_all(s, resp.data(), resp.size());
return;
}
+2 -1
View File
@@ -8,7 +8,7 @@
class HttpWsServer {
public:
HttpWsServer(db::Database& db, std::string host, uint16_t port);
HttpWsServer(db::Database& db, std::string host, uint16_t port, std::string maintenance_path = {});
~HttpWsServer();
bool start();
@@ -22,6 +22,7 @@ class HttpWsServer {
db::Database& db_;
std::string host_;
uint16_t port_;
std::string maintenance_path_;
std::atomic<bool> running_{false};
uintptr_t listen_sock_ = static_cast<uintptr_t>(-1);
std::thread accept_thread_;
+21 -8
View File
@@ -10,22 +10,31 @@
namespace fs = std::filesystem;
static void usage(const char* argv0) {
std::printf("Usage: %s [--host 0.0.0.0] [--port 8080] [--db data/aiserver.db] [--daemon]\n", argv0);
std::printf(
"Usage: %s [--host 0.0.0.0] [--port 8080] [--db data/aiserver.db] "
"[--maintenance data/maintenance.json] [--daemon]\n",
argv0);
}
int main(int argc, char** argv) {
std::string host = "0.0.0.0";
uint16_t port = 8080;
std::string db_path = "data/aiserver.db";
std::string maintenance_path;
bool daemon = false;
for (int i = 1; i < argc; ++i) {
const std::string a = argv[i];
if (a == "--host" && i + 1 < argc) host = argv[++i];
else if (a == "--port" && i + 1 < argc) port = static_cast<uint16_t>(std::stoi(argv[++i]));
else if (a == "--db" && i + 1 < argc) db_path = argv[++i];
else if (a == "--daemon") daemon = true;
if (a == "--host" && i + 1 < argc)
host = argv[++i];
else if (a == "--port" && i + 1 < argc)
port = static_cast<uint16_t>(std::stoi(argv[++i]));
else if (a == "--db" && i + 1 < argc)
db_path = argv[++i];
else if (a == "--maintenance" && i + 1 < argc)
maintenance_path = argv[++i];
else if (a == "--daemon")
daemon = true;
else if (a == "-h" || a == "--help") {
usage(argv[0]);
return 0;
@@ -36,6 +45,9 @@ int main(int argc, char** argv) {
}
}
if (maintenance_path.empty())
maintenance_path = (fs::path(db_path).parent_path() / "maintenance.json").string();
try {
fs::create_directories(fs::path(db_path).parent_path());
} catch (...) {
@@ -47,15 +59,16 @@ int main(int argc, char** argv) {
return 1;
}
HttpWsServer server(database, host, port);
HttpWsServer server(database, host, port, maintenance_path);
if (!server.start()) {
std::printf("Listen failed on %s:%u\n", host.c_str(), static_cast<unsigned>(port));
return 1;
}
std::printf("AIServer listening http://%s:%u (POST /register /login, WS /ws?pid=&token=)\n",
std::printf("AIServer listening http://%s:%u (POST /register /login, GET /maintenance, WS /ws)\n",
host.c_str(), static_cast<unsigned>(port));
std::printf("DB=%s\n", db_path.c_str());
std::printf("MaintenanceFile=%s\n", maintenance_path.c_str());
if (daemon) {
std::printf("daemon mode: running until process killed\n");
while (server.running()) {
+25
View File
@@ -0,0 +1,25 @@
#include "maintenance.hpp"
#include "json.hpp"
#include <fstream>
namespace maintenance {
State load(const std::string& path) {
State s;
if (path.empty()) return s;
std::ifstream in(path);
if (!in) return s;
try {
nlohmann::json j;
in >> j;
s.enabled = j.value("enabled", false);
s.message = j.value("message", s.message);
} catch (...) {
// ignore malformed
}
return s;
}
} // namespace maintenance
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <string>
namespace maintenance {
struct State {
bool enabled = false;
std::string message = "服务器维护中,请稍后再试。";
};
/// 读取 JSON:{"enabled":true,"message":"..."};文件不存在则 enabled=false。
State load(const std::string& path);
} // namespace maintenance