webthrottle: improved handling of world load/close and added auto reconnect

see #178
Dieser Commit ist enthalten in:
Reinder Feenstra 2025-01-15 23:57:25 +01:00
Ursprung 994cc4d99c
Commit 541a6f5fc9
7 geänderte Dateien mit 166 neuen und 31 gelöschten Zeilen

Datei anzeigen

@ -57,7 +57,7 @@ public:
WebSocketConnection(Server& server, std::shared_ptr<boost::beast::websocket::stream<boost::beast::tcp_stream>> ws, std::string_view idPrefix);
virtual ~WebSocketConnection();
void start();
virtual void start();
virtual void disconnect();
};

Datei anzeigen

@ -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<ObjectProperty<World>&>(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>& 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<WebThrottle>& WebThrottleConnection::getThrottle(uint32_t throttleId)
{
assert(isEventLoopThread());
@ -310,6 +355,13 @@ const std::shared_ptr<WebThrottle>& 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]()
{

Datei anzeigen

@ -31,13 +31,16 @@
#include "websocketconnection.hpp"
class WebThrottle;
class World;
class WebThrottleConnection : public WebSocketConnection
{
protected:
boost::beast::flat_buffer m_readBuffer;
std::queue<std::string> m_writeQueue;
boost::signals2::scoped_connection m_traintasticPropertyChanged;
std::map<uint32_t, std::shared_ptr<WebThrottle>> m_throttles;
std::map<uint32_t, boost::signals2::scoped_connection> m_throttleDestroying;
std::map<uint32_t, boost::signals2::scoped_connection> m_throttleReleased;
std::map<uint32_t, boost::signals2::scoped_connection> 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>& world);
const std::shared_ptr<WebThrottle>& getThrottle(uint32_t throttleId);
@ -58,6 +62,8 @@ public:
WebThrottleConnection(Server& server, std::shared_ptr<boost::beast::websocket::stream<boost::beast::tcp_stream>> ws);
virtual ~WebThrottleConnection();
void start() override;
};
#endif

Datei anzeigen

@ -92,7 +92,16 @@ inline static void deleteAll(T& objectList)
objectList.front()->active = false;
}
}
objectList.delete_(objectList.front());
if constexpr(std::is_same_v<T, ThrottleList>)
{
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);

Datei anzeigen

@ -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
{

Datei anzeigen

@ -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();
};
}
}

Datei anzeigen

@ -28,7 +28,13 @@
<button id="close_settings">Close</button>
</div>
</div>
<div id="throttles" class="flex-row"></div>
<div id="not-connected">
<div>Not connected, trying to connect...</div>
</div>
<div id="no-world" class="hide">
<div>No world loaded.</div>
</div>
<div id="throttles" class="flex-row hide"></div>
</div>
<script src="/js/throttle.js"></script>
</body>