mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-15 00:06:35 +00:00
Distinguish "room full" from "connection lost" in WebSocket error handling
Fix race condition where onerror clearing m_connectedFlag before onclose caused any network drop to be misidentified as a room-full rejection. Add m_wasEverConnected flag set once in onopen, use it in onclose to assign exit code 10 (room full) vs 11 (connection lost). Rename rejected API surface to disconnected to reflect the generalized meaning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
74271aa189
commit
680c7c28fe
@ -1300,7 +1300,7 @@ inline bool IsleApp::Tick()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef EXTENSIONS
|
#ifdef EXTENSIONS
|
||||||
if (Extensions::IsMultiplayerRejected()) {
|
if (Extensions::IsMultiplayerDisconnected()) {
|
||||||
g_closed = TRUE;
|
g_closed = TRUE;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class MultiplayerExt {
|
|||||||
static MxBool IsClonedCharacter(const char* p_name);
|
static MxBool IsClonedCharacter(const char* p_name);
|
||||||
static void HandleBeforeSaveLoad();
|
static void HandleBeforeSaveLoad();
|
||||||
static void HandleSaveLoaded();
|
static void HandleSaveLoaded();
|
||||||
static MxBool CheckRejected();
|
static MxBool CheckDisconnected();
|
||||||
|
|
||||||
static Multiplayer::NetworkManager* GetNetworkManager();
|
static Multiplayer::NetworkManager* GetNetworkManager();
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class MultiplayerExt {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#ifdef EXTENSIONS
|
#ifdef EXTENSIONS
|
||||||
LEGO1_EXPORT bool IsMultiplayerRejected();
|
LEGO1_EXPORT bool IsMultiplayerDisconnected();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace MP
|
namespace MP
|
||||||
@ -72,7 +72,7 @@ constexpr auto HandleROIClick = &MultiplayerExt::HandleROIClick;
|
|||||||
constexpr auto IsClonedCharacter = &MultiplayerExt::IsClonedCharacter;
|
constexpr auto IsClonedCharacter = &MultiplayerExt::IsClonedCharacter;
|
||||||
constexpr auto HandleBeforeSaveLoad = &MultiplayerExt::HandleBeforeSaveLoad;
|
constexpr auto HandleBeforeSaveLoad = &MultiplayerExt::HandleBeforeSaveLoad;
|
||||||
constexpr auto HandleSaveLoaded = &MultiplayerExt::HandleSaveLoaded;
|
constexpr auto HandleSaveLoaded = &MultiplayerExt::HandleSaveLoaded;
|
||||||
constexpr auto CheckRejected = &MultiplayerExt::CheckRejected;
|
constexpr auto CheckDisconnected = &MultiplayerExt::CheckDisconnected;
|
||||||
#else
|
#else
|
||||||
constexpr decltype(&MultiplayerExt::HandleCreate) HandleCreate = nullptr;
|
constexpr decltype(&MultiplayerExt::HandleCreate) HandleCreate = nullptr;
|
||||||
constexpr decltype(&MultiplayerExt::HandleWorldEnable) HandleWorldEnable = nullptr;
|
constexpr decltype(&MultiplayerExt::HandleWorldEnable) HandleWorldEnable = nullptr;
|
||||||
@ -82,7 +82,7 @@ constexpr decltype(&MultiplayerExt::HandleROIClick) HandleROIClick = nullptr;
|
|||||||
constexpr decltype(&MultiplayerExt::IsClonedCharacter) IsClonedCharacter = nullptr;
|
constexpr decltype(&MultiplayerExt::IsClonedCharacter) IsClonedCharacter = nullptr;
|
||||||
constexpr decltype(&MultiplayerExt::HandleBeforeSaveLoad) HandleBeforeSaveLoad = nullptr;
|
constexpr decltype(&MultiplayerExt::HandleBeforeSaveLoad) HandleBeforeSaveLoad = nullptr;
|
||||||
constexpr decltype(&MultiplayerExt::HandleSaveLoaded) HandleSaveLoaded = nullptr;
|
constexpr decltype(&MultiplayerExt::HandleSaveLoaded) HandleSaveLoaded = nullptr;
|
||||||
constexpr decltype(&MultiplayerExt::CheckRejected) CheckRejected = nullptr;
|
constexpr decltype(&MultiplayerExt::CheckDisconnected) CheckDisconnected = nullptr;
|
||||||
#endif
|
#endif
|
||||||
} // namespace MP
|
} // namespace MP
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class NetworkManager : public MxCore {
|
|||||||
void Connect(const char* p_roomId);
|
void Connect(const char* p_roomId);
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
bool IsConnected() const;
|
bool IsConnected() const;
|
||||||
bool WasRejected() const;
|
bool WasDisconnected() const;
|
||||||
|
|
||||||
void SetWalkAnimation(uint8_t p_walkAnimId);
|
void SetWalkAnimation(uint8_t p_walkAnimId);
|
||||||
void SetIdleAnimation(uint8_t p_idleAnimId);
|
void SetIdleAnimation(uint8_t p_idleAnimId);
|
||||||
@ -151,6 +151,7 @@ class NetworkManager : public MxCore {
|
|||||||
static const uint32_t BROADCAST_INTERVAL_MS = 66; // ~15Hz
|
static const uint32_t BROADCAST_INTERVAL_MS = 66; // ~15Hz
|
||||||
static const uint32_t TIMEOUT_MS = 5000; // 5 second timeout
|
static const uint32_t TIMEOUT_MS = 5000; // 5 second timeout
|
||||||
static const int EXIT_ROOM_FULL = 10;
|
static const int EXIT_ROOM_FULL = 10;
|
||||||
|
static const int EXIT_CONNECTION_LOST = 11;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Multiplayer
|
} // namespace Multiplayer
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class NetworkTransport {
|
|||||||
virtual void Connect(const char* p_roomId) = 0;
|
virtual void Connect(const char* p_roomId) = 0;
|
||||||
virtual void Disconnect() = 0;
|
virtual void Disconnect() = 0;
|
||||||
virtual bool IsConnected() const = 0;
|
virtual bool IsConnected() const = 0;
|
||||||
virtual bool WasRejected() const = 0;
|
virtual bool WasDisconnected() const = 0;
|
||||||
|
|
||||||
// Send binary data to all peers via relay
|
// Send binary data to all peers via relay
|
||||||
virtual void Send(const uint8_t* p_data, size_t p_length) = 0;
|
virtual void Send(const uint8_t* p_data, size_t p_length) = 0;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ class WebSocketTransport : public NetworkTransport {
|
|||||||
void Connect(const char* p_roomId) override;
|
void Connect(const char* p_roomId) override;
|
||||||
void Disconnect() override;
|
void Disconnect() override;
|
||||||
bool IsConnected() const override;
|
bool IsConnected() const override;
|
||||||
bool WasRejected() const override;
|
bool WasDisconnected() const override;
|
||||||
void Send(const uint8_t* p_data, size_t p_length) override;
|
void Send(const uint8_t* p_data, size_t p_length) override;
|
||||||
size_t Receive(std::function<void(const uint8_t*, size_t)> p_callback) override;
|
size_t Receive(std::function<void(const uint8_t*, size_t)> p_callback) override;
|
||||||
|
|
||||||
@ -25,7 +25,8 @@ class WebSocketTransport : public NetworkTransport {
|
|||||||
std::string m_relayBaseUrl;
|
std::string m_relayBaseUrl;
|
||||||
int m_socketId;
|
int m_socketId;
|
||||||
volatile int32_t m_connectedFlag; // Shared with JS main thread via Atomics
|
volatile int32_t m_connectedFlag; // Shared with JS main thread via Atomics
|
||||||
volatile int32_t m_rejectedFlag; // Set by JS when connection is rejected (e.g. room full)
|
volatile int32_t m_disconnectedFlag; // Set by JS when connection closes (room full or lost)
|
||||||
|
volatile int32_t m_wasEverConnected; // Set once in onopen, never cleared by error/close
|
||||||
uint8_t m_recvBuf[8192];
|
uint8_t m_recvBuf[8192];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -243,9 +243,9 @@ MxBool MultiplayerExt::IsClonedCharacter(const char* p_name)
|
|||||||
return s_networkManager->IsClonedCharacter(p_name) ? TRUE : FALSE;
|
return s_networkManager->IsClonedCharacter(p_name) ? TRUE : FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
MxBool MultiplayerExt::CheckRejected()
|
MxBool MultiplayerExt::CheckDisconnected()
|
||||||
{
|
{
|
||||||
if (s_networkManager && s_networkManager->WasRejected()) {
|
if (s_networkManager && s_networkManager->WasDisconnected()) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ Multiplayer::NetworkManager* MultiplayerExt::GetNetworkManager()
|
|||||||
return s_networkManager;
|
return s_networkManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Extensions::IsMultiplayerRejected()
|
bool Extensions::IsMultiplayerDisconnected()
|
||||||
{
|
{
|
||||||
return Extension<MultiplayerExt>::Call(MP::CheckRejected).value_or(FALSE);
|
return Extension<MultiplayerExt>::Call(MP::CheckDisconnected).value_or(FALSE);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,9 +185,9 @@ bool NetworkManager::IsConnected() const
|
|||||||
return m_transport && m_transport->IsConnected();
|
return m_transport && m_transport->IsConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkManager::WasRejected() const
|
bool NetworkManager::WasDisconnected() const
|
||||||
{
|
{
|
||||||
return m_transport && m_transport->WasRejected();
|
return m_transport && m_transport->WasDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkManager::OnWorldEnabled(LegoWorld* p_world)
|
void NetworkManager::OnWorldEnabled(LegoWorld* p_world)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ namespace Multiplayer
|
|||||||
{
|
{
|
||||||
|
|
||||||
WebSocketTransport::WebSocketTransport(const std::string& p_relayBaseUrl)
|
WebSocketTransport::WebSocketTransport(const std::string& p_relayBaseUrl)
|
||||||
: m_relayBaseUrl(p_relayBaseUrl), m_socketId(-1), m_connectedFlag(0), m_rejectedFlag(0)
|
: m_relayBaseUrl(p_relayBaseUrl), m_socketId(-1), m_connectedFlag(0), m_disconnectedFlag(0), m_wasEverConnected(0)
|
||||||
{
|
{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
MAIN_THREAD_EM_ASM({
|
MAIN_THREAD_EM_ASM({
|
||||||
@ -35,13 +35,15 @@ void WebSocketTransport::Connect(const char* p_roomId)
|
|||||||
|
|
||||||
std::string url = m_relayBaseUrl + "/room/" + p_roomId;
|
std::string url = m_relayBaseUrl + "/room/" + p_roomId;
|
||||||
|
|
||||||
m_rejectedFlag = 0;
|
m_disconnectedFlag = 0;
|
||||||
|
m_wasEverConnected = 0;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
m_socketId = MAIN_THREAD_EM_ASM_INT({
|
m_socketId = MAIN_THREAD_EM_ASM_INT({
|
||||||
var url = UTF8ToString($0);
|
var url = UTF8ToString($0);
|
||||||
var connPtr = $1;
|
var connPtr = $1;
|
||||||
var rejPtr = $2;
|
var discPtr = $2;
|
||||||
|
var everConnPtr = $3;
|
||||||
var socketId = Module._mpNextSocketId++;
|
var socketId = Module._mpNextSocketId++;
|
||||||
Module._mpMessageQueues[socketId] = [];
|
Module._mpMessageQueues[socketId] = [];
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ void WebSocketTransport::Connect(const char* p_roomId)
|
|||||||
|
|
||||||
ws.onopen = function() {
|
ws.onopen = function() {
|
||||||
Atomics.store(HEAP32, connPtr >> 2, 1);
|
Atomics.store(HEAP32, connPtr >> 2, 1);
|
||||||
|
Atomics.store(HEAP32, everConnPtr >> 2, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = function(event) {
|
ws.onmessage = function(event) {
|
||||||
@ -61,13 +64,12 @@ void WebSocketTransport::Connect(const char* p_roomId)
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = function() {
|
ws.onclose = function() {
|
||||||
var wasConnected = Atomics.load(HEAP32, connPtr >> 2);
|
var wasEverConnected = Atomics.load(HEAP32, everConnPtr >> 2);
|
||||||
Atomics.store(HEAP32, connPtr >> 2, 0);
|
Atomics.store(HEAP32, connPtr >> 2, 0);
|
||||||
if (!wasConnected) {
|
Atomics.store(HEAP32, discPtr >> 2, 1);
|
||||||
// Never connected — server rejected (room full / 503)
|
// 10 = room full / server rejected before connecting
|
||||||
Atomics.store(HEAP32, rejPtr >> 2, 1);
|
// 11 = connection lost after successful connect
|
||||||
Module._exitCode = 10;
|
Module._exitCode = wasEverConnected ? 11 : 10;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = function() {
|
ws.onerror = function() {
|
||||||
@ -81,7 +83,7 @@ void WebSocketTransport::Connect(const char* p_roomId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return socketId;
|
return socketId;
|
||||||
}, url.c_str(), &m_connectedFlag, &m_rejectedFlag);
|
}, url.c_str(), &m_connectedFlag, &m_disconnectedFlag, &m_wasEverConnected);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if (m_socketId <= 0) {
|
if (m_socketId <= 0) {
|
||||||
@ -105,6 +107,7 @@ void WebSocketTransport::Disconnect()
|
|||||||
|
|
||||||
m_socketId = -1;
|
m_socketId = -1;
|
||||||
m_connectedFlag = 0;
|
m_connectedFlag = 0;
|
||||||
|
m_wasEverConnected = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,9 +116,9 @@ bool WebSocketTransport::IsConnected() const
|
|||||||
return m_socketId > 0 && m_connectedFlag != 0;
|
return m_socketId > 0 && m_connectedFlag != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketTransport::WasRejected() const
|
bool WebSocketTransport::WasDisconnected() const
|
||||||
{
|
{
|
||||||
return m_rejectedFlag != 0;
|
return m_disconnectedFlag != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::Send(const uint8_t* p_data, size_t p_length)
|
void WebSocketTransport::Send(const uint8_t* p_data, size_t p_length)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user