diff --git a/client/dfclient/include/game_data.hpp b/client/dfclient/include/game_data.hpp index e43cc21..f65fad8 100644 --- a/client/dfclient/include/game_data.hpp +++ b/client/dfclient/include/game_data.hpp @@ -21,8 +21,6 @@ struct GameData { std::vector players; std::string levelData; bool gameInProgress = false; - - WebSocket* socket = nullptr; }; extern GameData gameData; diff --git a/client/dfclient/include/websocket.hpp b/client/dfclient/include/websocket.hpp index cc9a79c..e8d69d8 100644 --- a/client/dfclient/include/websocket.hpp +++ b/client/dfclient/include/websocket.hpp @@ -10,7 +10,8 @@ struct WebSocket { virtual int Connect(std::string& address) = 0; - virtual bool Send(std::string& data) = 0; + virtual bool Send(const std::string& data) = 0; + virtual void Close() = 0; virtual ~WebSocket() {} // TODO use lock-free queue instead? @@ -19,12 +20,8 @@ struct WebSocket { std::vector messageQueue; }; -struct WebSocketManager { - Messages GetMessages(); +Messages GetMessages(); - std::unique_ptr _ws; -}; - -WebSocket* CreateWebSocket(); +void CreateWebSocket(); -extern WebSocketManager webSocketManager; +extern std::unique_ptr GlobalWebsocket; diff --git a/client/dfclient/src/menu_state.cpp b/client/dfclient/src/menu_state.cpp index 84f0d8b..feb7f45 100644 --- a/client/dfclient/src/menu_state.cpp +++ b/client/dfclient/src/menu_state.cpp @@ -41,7 +41,6 @@ void MenuState::ShowMessage(std::string message) [text] (tweeny::tween& t, int v) -> bool { auto textColor = text->color(); text->setColor(textColor.r(), textColor.g(), textColor.b(), v); - std::cout << "v " << v << "\n"; if (v == 0) delete text; return false; @@ -61,6 +60,8 @@ MenuState::MenuState(Resources& r) : _resources(r) { TextCreator textCreator(r); std::cout << "menu game in progress\n"; ShowMessage("game already in progress"); + GlobalWebsocket->Close(); + GlobalWebsocket = nullptr; gameData.gameInProgress = false; } boost::property_tree::ptree pt; @@ -91,8 +92,8 @@ MenuState::MenuState(Resources& r) : _resources(r) { void MenuState::TryConnect() { gameData.serverAddress = "ws://" + gameData.serverAddress; std::cout << "server " << gameData.serverAddress << ", my nickname " << gameData.myNickname << "\n"; - gameData.socket = CreateWebSocket(); - int ret = gameData.socket->Connect(gameData.serverAddress); + CreateWebSocket(); + int ret = GlobalWebsocket->Connect(gameData.serverAddress); if (ret < 0) { std::cout << "socket->Connect failed " << ret << "\n"; this->ShowMessage("connecting to server failed"); diff --git a/client/dfclient/src/state_manager.cpp b/client/dfclient/src/state_manager.cpp index 967a42f..4ce662c 100644 --- a/client/dfclient/src/state_manager.cpp +++ b/client/dfclient/src/state_manager.cpp @@ -62,7 +62,7 @@ TextCreator StateManager::CreateTextCreator() { void StateManager::OnFrameStart() { _resources.UpdateTweens(); - auto nextStateType = _currentState->Update(webSocketManager.GetMessages()); + auto nextStateType = _currentState->Update(GetMessages()); if (nextStateType == _currentStateType) { return; } diff --git a/client/dfclient/src/util.cpp b/client/dfclient/src/util.cpp index 911de7e..d595ef3 100644 --- a/client/dfclient/src/util.cpp +++ b/client/dfclient/src/util.cpp @@ -16,7 +16,7 @@ void SendData(flatbuffers::FlatBufferBuilder& builder) { auto size = builder.GetSize(); auto str = std::string(data, data + size); - gameData.socket->Send(str); + GlobalWebsocket->Send(str); } std::vector createArcTexels(float outerRadius, float innerRadius, int degrees) diff --git a/client/dfclient/src/websocket.cpp b/client/dfclient/src/websocket.cpp index a5796eb..fc55c95 100644 --- a/client/dfclient/src/websocket.cpp +++ b/client/dfclient/src/websocket.cpp @@ -2,21 +2,21 @@ #include -WebSocketManager webSocketManager; +std::unique_ptr GlobalWebsocket; // Runs in game loop thread -Messages WebSocketManager::GetMessages() { +Messages GetMessages() { Messages m; - if (!_ws) { + if (!GlobalWebsocket) { return m; } - std::lock_guard guard(_ws->mq_mutex); - m.opened = _ws->toBeOpened; - m.data_msgs.swap(_ws->messageQueue); + std::lock_guard guard(GlobalWebsocket->mq_mutex); + m.opened = GlobalWebsocket->toBeOpened; + m.data_msgs.swap(GlobalWebsocket->messageQueue); if (m.opened) { - _ws->toBeOpened = false; + GlobalWebsocket->toBeOpened = false; } return m; @@ -30,7 +30,8 @@ class WebSocketEmscripten { public: int Connect(std::string& address) override; - bool Send(std::string& data) override; + bool Send(const std::string& data) override; + void Close() override; virtual ~WebSocketEmscripten() {} EMSCRIPTEN_WEBSOCKET_T _socket; @@ -84,28 +85,26 @@ int WebSocketEmscripten::Connect(std::string& address) { return 0; } -bool WebSocketEmscripten::Send(std::string& data) { - return emscripten_websocket_send_binary(this->_socket, data.data(), data.size()) == EMSCRIPTEN_RESULT_SUCCESS; +bool WebSocketEmscripten::Send(const std::string& data) { + return emscripten_websocket_send_binary(this->_socket, (void*) data.data(), data.size()) == EMSCRIPTEN_RESULT_SUCCESS; } -#else // __EMSCRIPTEN__ +void WebSocketEmscripten::Close() { + std::cout << "TODO implement\n"; + exit(1); +} -class WebSocketBeast - : public WebSocket -{ -public: - int Connect(std::string& address) override; - bool Send(std::string& data) override; - virtual ~WebSocketBeast() {} +#else // __EMSCRIPTEN__ -}; +class WebSocketBeast; typedef WebSocketBeast WebSocketType; -#include -#include #include #include +#include +#include +#include #include #include #include @@ -117,9 +116,22 @@ namespace websocket = beast::websocket; // from namespace net = boost::asio; // from using tcp = boost::asio::ip::tcp; // from -net::io_context ioc; -websocket::stream ws{ioc}; -beast::flat_buffer buffer; +struct beastContext { + net::io_context ioc; + websocket::stream ws{ioc}; + beast::flat_buffer buffer; +}; + +class WebSocketBeast + : public WebSocket +{ +public: + int Connect(std::string& address) override; + bool Send(const std::string& data) override; + void Close() override; + virtual ~WebSocketBeast() {} + beastContext ctx; +}; void DoRead(); @@ -131,27 +143,32 @@ void OnRead(beast::error_code ec, std::size_t bytes_transferred) { if (ec.value() == boost::system::errc::operation_canceled) { // this websocket has disconnected // todo: cleanup whatever is happening and return to main menu + std::cout << "operation canceled\n"; return; } std::cout << "read failed " << ec << "\n"; } - auto str = beast::buffers_to_string(buffer.data()); - WebSocketBeast* wsb = (WebSocketBeast*) webSocketManager._ws.get(); + WebSocketBeast* wsb = (WebSocketBeast*) GlobalWebsocket.get(); + auto str = beast::buffers_to_string(wsb->ctx.buffer.data()); std::lock_guard guard(wsb->mq_mutex); wsb->messageQueue.push_back(str); - buffer.consume(buffer.size()); + wsb->ctx.buffer.consume(wsb->ctx.buffer.size()); DoRead(); } void DoRead() { - ws.async_read(buffer, &OnRead); + WebSocketBeast* wsb = (WebSocketBeast*) GlobalWebsocket.get(); + wsb->ctx.ws.async_read(wsb->ctx.buffer, &OnRead); } int WebSocketBeast::Connect(std::string& fullAddress) { - tcp::resolver resolver{ioc}; - ws.binary(true); + WebSocketBeast* wsb = (WebSocketBeast*) GlobalWebsocket.get(); + + tcp::resolver resolver{wsb->ctx.ioc}; + + wsb->ctx.ws.binary(true); auto address = fullAddress.substr(std::string("ws://").size(), fullAddress.size()); @@ -164,38 +181,54 @@ int WebSocketBeast::Connect(std::string& fullAddress) { auto port = address.substr(colon + 1, address.size()); auto const results = resolver.resolve(host, port); try { - auto ep = net::connect(ws.next_layer(), results); + auto ep = net::connect(wsb->ctx.ws.next_layer(), results); host += ':' + std::to_string(ep.port()); - ws.set_option(websocket::stream_base::decorator( + wsb->ctx.ws.set_option(websocket::stream_base::decorator( [](websocket::request_type& req) { req.set(http::field::user_agent, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro"); })); - ws.handshake(host, "/"); + wsb->ctx.ws.handshake(host, "/"); } catch (std::exception e) { return -1; } - webSocketManager._ws->toBeOpened = true; + GlobalWebsocket->toBeOpened = true; DoRead(); new std::thread([&](){ - ioc.run(); + try { + wsb->ctx.ioc.run(); + } catch (const boost::exception& e) { + std::cout << "wsb->ctx.ioc.run() failed: " << boost::diagnostic_information(e) << "\n"; + } + std::cout << "ioc quitting\n"; }); return 0; } -bool WebSocketBeast::Send(std::string& data) { - ws.write(net::buffer(std::string(data))); +bool WebSocketBeast::Send(const std::string& data) { + WebSocketBeast* wsb = (WebSocketBeast*) GlobalWebsocket.get(); + wsb->ctx.ws.write(net::buffer(std::string(data))); +} + +void WebSocketBeast::Close() { + WebSocketBeast* wsb = (WebSocketBeast*) GlobalWebsocket.get(); + try { + wsb->ctx.ws.close(websocket::close_code::normal); + } catch (const std::exception& ex) { + std::cout << "exception occured while closing websocket: " << ex.what() << std::endl; + } catch (...) { + std::cout << "an unknown exception occured while closing websocket" << std::endl; + } } #endif -WebSocket* CreateWebSocket() { - webSocketManager._ws = std::make_unique(); - return webSocketManager._ws.get(); +void CreateWebSocket() { + GlobalWebsocket = std::make_unique(); // this deletes the old one } diff --git a/server/game_thread.cpp b/server/game_thread.cpp index 45f26f8..cc93720 100644 --- a/server/game_thread.cpp +++ b/server/game_thread.cpp @@ -68,7 +68,6 @@ std::string makeServerMessage(flatbuffers::FlatBufferBuilder &builder, void sendGameAlreadyInProgress(dfws::Handle hdl) { - std::cout << "sending game already in progress"; flatbuffers::FlatBufferBuilder builder; auto offset = FlatBuffGenerated::CreateSimpleServerEvent(builder, FlatBuffGenerated::SimpleServerEventType_GameAlreadyInProgress); @@ -510,7 +509,9 @@ void gameOnMessage(dfws::Handle hdl, const std::string& payload) auto p = getPlayerByConnHdl(hdl); if (!p) { + std::cout << "gameOnMessage: game already in progress\n"; sendGameAlreadyInProgress(hdl); + dfws::Close(hdl); return; } if (p->state == MobState::ATTACKING) diff --git a/server/websocket.cpp b/server/websocket.cpp index 9b520ee..1ae8bf5 100644 --- a/server/websocket.cpp +++ b/server/websocket.cpp @@ -141,18 +141,38 @@ class DfWebsocket : public std::enable_shared_from_this { // sync write ws_.write(net::buffer(data)); } + + void Close() { + ws_.close(websocket::close_code::normal); + } }; -void dfws::SendData(Handle hdl, const std::string& data) +std::shared_ptr getSocketForHandle(dfws::Handle hdl) { - for (auto& s : sockets) { - if (s->socketID_ == hdl) { - s->Send(data); - return; - } + auto it = std::find_if(sockets.begin(), sockets.end(), [&] (const auto& s) { + return s->socketID_ == hdl; + }); + if (it == sockets.end()) { + std::cout << "could not find socket with id " << hdl << "\n"; + exit(1); } - std::cout << "could not find socket with id " << hdl << "\n"; - exit(1); + return *it; +} + +void dfws::SendData(Handle hdl, const std::string& data) +{ + auto s = getSocketForHandle(hdl); + s->Send(data); +} + +void dfws::Close(Handle hdl) +{ + std::cout << "closing handle " << hdl << "\n"; + auto it = std::find_if(sockets.begin(), sockets.end(), [&] (const auto& s) { + return s->socketID_ == hdl; + }); + (*it)->Close(); + sockets.erase(it); } void dfwsOnAccept(beast::error_code ec, tcp::socket socket) @@ -160,7 +180,7 @@ void dfwsOnAccept(beast::error_code ec, tcp::socket socket) std::cout << "on accept\n"; if (ec) return fail(ec, "accept"); - // websocket::stream ws_(std::move(socket)); + auto socketPtr = std::make_shared(std::move(socket), lastSocketID++); sockets.push_back(socketPtr); sockets.back()->start(); @@ -210,4 +230,5 @@ void dfws::Run(unsigned short port) acceptor.async_accept(&dfwsOnAccept); ioc.run(); + std::cout << "ioc quitting\n"; } diff --git a/server/websocket.hpp b/server/websocket.hpp index 0897699..d622b23 100644 --- a/server/websocket.hpp +++ b/server/websocket.hpp @@ -19,5 +19,6 @@ void SetOnMessage(OnMessageHandler msgHandler); void SetOnOpen(OnOpenHandler handler); void SetOnClose(OnCloseHandler handler); void Run(unsigned short port); +void Close(Handle hdl); };