Handle reconnection for godot-socketio#7
Conversation
- Implement reconnect logic - Handle _on_noop
godot-socketio
godot-socketiogodot-socketio
|
Thanks again for this amazing addon @msabaeian, thanks to you, I could make my card game so much more reliable. If this PR makes sense to you, I am really happy to contribute; otherwise, I will remain on the fork I have. Thanks again for taking care of such a complicated problem |
|
Hey @garciaf! thanks for your work! I’ll review this in the coming week and if everything looks good, merge it. |
|
All good @msabaeian if you want to review it in the game I am working on, I can give you further access to the prototype I have. But basically, I use this plugin to allow people to play the Godot game on their phone (With a web browser). And Socket IO is so much more reliable than Websocket. Your work is incredible. |
msabaeian
left a comment
There was a problem hiding this comment.
thanks for your effort, i've some question and suggestions
|
|
||
| var session_id: String = "" | ||
| var state: State = State.DISCONNECTED | ||
| var state = State.DISCONNECTED |
There was a problem hiding this comment.
Question: why removing state type?
There was a problem hiding this comment.
@msabaeian This was failing on Godot 4.6
maybe I could try
var state := State.DISCONNECTED
But Godot 4.6 would fail at this point. I would need to investigate the nature of the error
| @export var path: String = "/engine.io" | ||
| @export var auto_reconnect: bool = true | ||
| @export var max_reconnect_attempts: int = 5 | ||
| @export var reconnect_base_delay: float = 1.0 |
There was a problem hiding this comment.
Nit: it would be good to mention type of this value, seconds or milliseconds
There was a problem hiding this comment.
Very good point.
would
@export var reconnect_base_delay_seconds: float = 1.0
Be what you think? Do you have some conventions in the project? When it comes to defined durations?
| signal engine_connection_closed() | ||
| signal engine_message_received(data: String) | ||
| signal engine_transport_upgraded() | ||
| signal reconnect_attempt(attempt: int, max: int) |
There was a problem hiding this comment.
| signal reconnect_attempt(attempt: int, max: int) | |
| signal reconnection_attempted(attempt: int, max: int) |
| engine_close() | ||
| if auto_reconnect and _reconnect_attempts < max_reconnect_attempts: | ||
| _reconnect_attempts += 1 | ||
| reconnect_attempt.emit(_reconnect_attempts, max_reconnect_attempts) |
There was a problem hiding this comment.
Question: wouldn't it better to emit this after attempting not before it? not sure what's the behavior in socketio library itself
There was a problem hiding this comment.
We could indeed but since everything is wired via signal we technically do not know when this is done. But I could put right after t.start()
That's what you have in mind right?
| session_id = "" | ||
| state = State.DISCONNECTED | ||
| _websocket = null | ||
| _polling_http_request = null | ||
| _send_data_http_request = null | ||
| _send_data_queue.clear() | ||
| _probe_sent = false | ||
| _transport_type = TransportType.POLLING | ||
| _ping_interval = 0 | ||
| _pong_timeout = 0 | ||
| _max_payload = 0 |
There was a problem hiding this comment.
| session_id = "" | |
| state = State.DISCONNECTED | |
| _websocket = null | |
| _polling_http_request = null | |
| _send_data_http_request = null | |
| _send_data_queue.clear() | |
| _probe_sent = false | |
| _transport_type = TransportType.POLLING | |
| _ping_interval = 0 | |
| _pong_timeout = 0 | |
| _max_payload = 0 | |
| _clear_values() |
| disconnect_namespace(ns) | ||
|
|
||
| engine_close() | ||
| socket_disconnected.emit() |
|
|
||
| func _on_pong(): | ||
| func _on_pong(payload: String = ""): | ||
| if payload != "probe": |
There was a problem hiding this comment.
i don't think socketio ever sends a pong packet which doesn't have probe, but let's add a warning here in case of those unexpected scenarios
There was a problem hiding this comment.
Not a failing one, right? Just a warning in the console?
What do you use usually?
|
@msabaeian thank you so much for looking into it and very good comment. I will fix it and push it. We might also consider chaning the Readme afterward. |
Bug fixes
_on_noop()treated NOOP as an errorNOOP is a normal Engine.IO packet sent during polling to unblock a long-poll request. The handler was calling
push_error(). It now calls_poll(), matching the behaviour of_on_message().disconnect_socket()emittedsocket_disconnectedtwicedisconnect_socket()calledengine_close()which already emitsengine_connection_closed→_on_engine_io_connection_closed()→socket_disconnected. The duplicate directsocket_disconnected.emit()at the end ofdisconnect_socket()was removed.Upgrade PONG and heartbeat PONG not distinguished
_on_pong()was unconditionally triggering the WebSocket upgrade flow. It now receives the raw payload and only upgrades when the payload is"probe", correctly ignoring plain heartbeat PONGs from the server.StateandTransportTypeenum type annotation errorIn Godot 4, inner enums on a
class_nameclass cause a type mismatch between the unqualified (State) and fully qualified (EngineIO.State) names when used as type annotations. Removed the explicit type annotations onstateand_transport_type, letting GDScript infer them from their initialisers.Client-initiated PINGs broke Engine.IO v4
In Engine.IO v4 only the server sends PINGs — client-initiated PINGs are v3 behaviour and cause the server to disconnect with
transport error. A heartbeat timer that was sending unsolicited PINGs onpingIntervalhas been removed. The existing_on_ping()→ PONG response path is sufficient for keepalive.New feature: automatic reconnection
When the WebSocket closes unexpectedly, the client now attempts to reconnect automatically with linear backoff instead of immediately calling
engine_close().Three new exported variables are exposed in the Inspector:
auto_reconnecttruemax_reconnect_attempts5reconnect_base_delay1.0A new signal
reconnect_attempt(attempt: int, max: int)is emitted on each retry so the game UI can show feedback like "Reconnecting 2/5…".On a successful reconnection
_reconnect_attemptsresets to0at both success paths (polling in_on_open, WebSocket in_on_pong). If all attempts are exhausted,engine_close()is called normally andengine_connection_closedfires once.