From 541a6f5fc9d2b1e6360586fbcd43c6195d72bc25 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 15 Jan 2025 23:57:25 +0100 Subject: [PATCH] webthrottle: improved handling of world load/close and added auto reconnect see #178 --- server/src/network/websocketconnection.hpp | 2 +- server/src/network/webthrottleconnection.cpp | 52 ++++++++++++ server/src/network/webthrottleconnection.hpp | 6 ++ server/src/world/world.cpp | 12 ++- server/www/css/throttle.css | 34 +++++++- server/www/js/throttle.js | 83 ++++++++++++++------ server/www/throttle.html | 8 +- 7 files changed, 166 insertions(+), 31 deletions(-) diff --git a/server/src/network/websocketconnection.hpp b/server/src/network/websocketconnection.hpp index f871833f..f59be644 100644 --- a/server/src/network/websocketconnection.hpp +++ b/server/src/network/websocketconnection.hpp @@ -57,7 +57,7 @@ public: WebSocketConnection(Server& server, std::shared_ptr> ws, std::string_view idPrefix); virtual ~WebSocketConnection(); - void start(); + virtual void start(); virtual void disconnect(); }; diff --git a/server/src/network/webthrottleconnection.cpp b/server/src/network/webthrottleconnection.cpp index c98fbd89..ad6eaa0e 100644 --- a/server/src/network/webthrottleconnection.cpp +++ b/server/src/network/webthrottleconnection.cpp @@ -44,6 +44,13 @@ WebThrottleConnection::~WebThrottleConnection() { assert(isEventLoopThread()); + // disconnect all signals: + m_traintasticPropertyChanged.disconnect(); + m_trainPropertyChanged.clear(); + m_throttleReleased.clear(); + m_throttleDestroying.clear(); + + // destroy all throttles: for(auto& it : m_throttles) { it.second->destroy(); @@ -51,6 +58,27 @@ WebThrottleConnection::~WebThrottleConnection() } } +void WebThrottleConnection::start() +{ + WebSocketConnection::start(); + + EventLoop::call( + [this]() + { + m_traintasticPropertyChanged = Traintastic::instance->propertyChanged.connect( + [this](BaseProperty& property) + { + if(property.name() == "world") + { + assert(m_throttles.empty()); + sendWorld(static_cast&>(property).value()); + } + }); + + sendWorld(Traintastic::instance->world.value()); + }); +} + void WebThrottleConnection::doRead() { assert(isServerThread()); @@ -294,6 +322,23 @@ void WebThrottleConnection::sendError(uint32_t throttleId, std::error_code ec) } } +void WebThrottleConnection::sendWorld(const std::shared_ptr& world) +{ + assert(isEventLoopThread()); + + auto event = nlohmann::json::object(); + event.emplace("event", "world"); + if(world) + { + event.emplace("name", world->name.toJSON()); + } + else + { + event.emplace("name", nullptr); + } + sendMessage(event); +} + const std::shared_ptr& WebThrottleConnection::getThrottle(uint32_t throttleId) { assert(isEventLoopThread()); @@ -310,6 +355,13 @@ const std::shared_ptr& WebThrottleConnection::getThrottle(uint32_t auto [it, inserted] = m_throttles.emplace(throttleId, WebThrottle::create(*world)); if(inserted) /*[[likely]]*/ { + m_throttleDestroying.emplace(throttleId, it->second->onDestroying.connect( + [this, throttleId](Object& /*object*/) + { + released(throttleId); + m_throttleDestroying.erase(throttleId); + m_throttles.erase(throttleId); + })); m_throttleReleased.emplace(throttleId, it->second->released.connect( [this, throttleId]() { diff --git a/server/src/network/webthrottleconnection.hpp b/server/src/network/webthrottleconnection.hpp index 4b5b4aef..83271adc 100644 --- a/server/src/network/webthrottleconnection.hpp +++ b/server/src/network/webthrottleconnection.hpp @@ -31,13 +31,16 @@ #include "websocketconnection.hpp" class WebThrottle; +class World; class WebThrottleConnection : public WebSocketConnection { protected: boost::beast::flat_buffer m_readBuffer; std::queue m_writeQueue; + boost::signals2::scoped_connection m_traintasticPropertyChanged; std::map> m_throttles; + std::map m_throttleDestroying; std::map m_throttleReleased; std::map m_trainPropertyChanged; @@ -48,6 +51,7 @@ protected: void sendMessage(const nlohmann::json& message); void sendError(uint32_t throttleId, std::string_view text, std::string_view tag = {}); void sendError(uint32_t throttleId, std::error_code ec); + void sendWorld(const std::shared_ptr& world); const std::shared_ptr& getThrottle(uint32_t throttleId); @@ -58,6 +62,8 @@ public: WebThrottleConnection(Server& server, std::shared_ptr> ws); virtual ~WebThrottleConnection(); + + void start() override; }; #endif diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index c0989ebb..da4b31fc 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -92,7 +92,16 @@ inline static void deleteAll(T& objectList) objectList.front()->active = false; } } - objectList.delete_(objectList.front()); + if constexpr(std::is_same_v) + { + auto& throttle = objectList[0]; + throttle->destroy(); + objectList.removeObject(throttle); + } + else + { + objectList.delete_(objectList.front()); + } } } @@ -434,6 +443,7 @@ World::~World() deleteAll(*inputs); deleteAll(*identifications); deleteAll(*boards); + deleteAll(*throttles); deleteAll(*trains); deleteAll(*railVehicles); deleteAll(*luaScripts); diff --git a/server/www/css/throttle.css b/server/www/css/throttle.css index 06ef6397..6ae2dc50 100644 --- a/server/www/css/throttle.css +++ b/server/www/css/throttle.css @@ -45,7 +45,7 @@ .hide { - display: none; + display: none !important; } .stretch @@ -76,6 +76,7 @@ html { font-family: sans-serif; + height: -webkit-fill-available; } body @@ -109,11 +110,12 @@ header h1 #body { position: relative; + height: 100%; } #settings { - z-index: 1; + z-index: 3; position: absolute; background-color: #121212; width: 100%; @@ -126,6 +128,34 @@ header h1 display: block; } +#not-connected +{ + z-index: 2; + position: absolute; + display: flex; + align-items: center; + width: 100%; + height: 100%; + padding: 0.5rem; +} + +#no-world +{ + z-index: 1; + position: absolute; + display: flex; + align-items: center; + width: 100%; + height: 100%; + padding: 0.5rem; +} + +#not-connected div, +#no-world div +{ + margin: 0 auto; +} + button.control, select.control { diff --git a/server/www/js/throttle.js b/server/www/js/throttle.js index 87f66b27..b14112ea 100644 --- a/server/www/js/throttle.js +++ b/server/www/js/throttle.js @@ -301,16 +301,13 @@ var tm = new function () localStorage.throttleName = document.getElementById('throttle_name').value; localStorage.throttleStopOnRelease = document.getElementById('stop_train_on_release').value == 'on'; document.getElementById('settings').classList.add('hide'); - if(tm.throttles.length == 0) - { - tm.add(); - } + this.connect(); }; if(localStorage.throttleName && localStorage.throttleStopOnRelease) { document.getElementById('settings').classList.add('hide'); - this.add(); + this.connect(); } } @@ -331,31 +328,60 @@ var tm = new function () this.ws = new WebSocket((window.location.protocol == 'https' ? 'wss' : 'ws') + '://' + window.location.host + window.location.pathname); this.ws.onopen = function (ev) { - tm.send({ 'action': 'get_train_list' }); - tm.throttles.forEach(function (throttle, _) - { - tm.send({ - 'throttle_id': throttle.id, - 'action': 'set_name', - 'value': localStorage.throttleName + ' #' + throttle.id, - }); - var trainId = localStorage['throttle' + throttle.id + 'TrainId']; - if(trainId) - { - tm.send({ - 'throttle_id': throttle.id, - 'action': 'acquire', - 'train_id': trainId, - 'steal': false, - }); - } - }); + console.log('onopen'); + document.getElementById('not-connected').classList.add('hide'); }; this.ws.onmessage = function (ev) { var msg = JSON.parse(ev.data); console.log('RX', msg); - if(msg['event'] == 'train_list') + if(msg['event'] == 'world') + { + if(msg.name === null) + { + document.getElementById('throttles').replaceChildren(); + document.getElementById('throttles').classList.add('hide'); + document.getElementById('no-world').classList.remove('hide'); + } + else + { + document.getElementById('throttles').classList.remove('hide'); + document.getElementById('no-world').classList.add('hide'); + if(!document.getElementById('throttles').hasChildNodes()) + { + tm.add(); + } + else + { + tm.throttles.forEach(function (throttle, _) + { + throttle.setTrainList([]); // clear train list + }); + } + + tm.send({ 'action': 'get_train_list' }); + + tm.throttles.forEach(function (throttle, _) + { + tm.send({ + 'throttle_id': throttle.id, + 'action': 'set_name', + 'value': localStorage.throttleName + ' #' + throttle.id, + }); + const trainId = localStorage['throttle' + throttle.id + 'TrainId']; + if(trainId) + { + tm.send({ + 'throttle_id': throttle.id, + 'action': 'acquire', + 'train_id': trainId, + 'steal': false, + }); + } + }); + } + } + else if(msg['event'] == 'train_list') { tm.throttles.forEach(function (throttle, _) { @@ -404,12 +430,17 @@ var tm = new function () }; this.ws.onclose = function () { + console.log('onclose'); + document.getElementById('not-connected').classList.remove('hide'); + document.getElementById('throttles').classList.add('hide'); + document.getElementById('no-world').classList.add('hide'); + tm.ws = null; setTimeout(function () { tm.connect(); }, 1000); } this.ws.onerror = function (err) { console.error('WebSocket error: ', err.message); - this.ws.close(); + tm.ws.close(); }; } } diff --git a/server/www/throttle.html b/server/www/throttle.html index d6509eb8..cd2ece51 100644 --- a/server/www/throttle.html +++ b/server/www/throttle.html @@ -28,7 +28,13 @@ -
+
+
Not connected, trying to connect...
+
+
+
No world loaded.
+
+