fix(network): prevent null multiplayer peer crashes offline/teardown and fix Nakama RPC session validation
This commit is contained in:
@@ -48,6 +48,12 @@ var server_version := ""
|
||||
|
||||
var dispatcher
|
||||
var log_buffer
|
||||
var surfaced_error_tracker
|
||||
## Set by plugin.gd. Lets the per-frame play-state poll end game-run
|
||||
## bookkeeping when the game exits on its own (self-quit, crash) — the
|
||||
## debugger session's stopped signal is not reliably connected, and no MCP
|
||||
## stop op runs in that path (#642).
|
||||
var debugger_plugin
|
||||
## Set by plugin.gd when the HTTP port is occupied by an incompatible or
|
||||
## unverified server. Keeping the Connection node alive lets handlers and the
|
||||
## dock share one object, but no WebSocket is opened to the wrong server.
|
||||
@@ -89,6 +95,10 @@ func _process(delta: float) -> void:
|
||||
if pause_processing:
|
||||
return
|
||||
_peer.poll()
|
||||
## Run-stop bookkeeping must not wait behind the socket-state machine:
|
||||
## if the game stops while disconnected, the first command drained on
|
||||
## reconnect would still observe stale "live" state (PR #642 review).
|
||||
_check_game_run_play_state(EditorInterface.is_playing_scene())
|
||||
|
||||
match _peer.get_ready_state():
|
||||
WebSocketPeer.STATE_OPEN:
|
||||
@@ -309,6 +319,8 @@ func send_deferred_response(request_id: String, payload: Dictionary) -> void:
|
||||
## `readiness_after` payload field were ever dropped.
|
||||
if not response.has("readiness"):
|
||||
response["readiness"] = get_readiness()
|
||||
if not response.has("error_watermark"):
|
||||
_stamp_error_watermark(response)
|
||||
if _send_json(response) and dispatcher != null:
|
||||
dispatcher.complete_deferred_response(request_id)
|
||||
|
||||
@@ -319,10 +331,15 @@ func _hook_editor_signals() -> void:
|
||||
EditorInterface.get_editor_settings() # ensure interface is ready
|
||||
_last_scene_path = _get_current_scene_path()
|
||||
_last_play_state = EditorInterface.is_playing_scene()
|
||||
_last_play_state_for_run = _last_play_state
|
||||
|
||||
|
||||
var _last_scene_path := ""
|
||||
var _last_play_state := false
|
||||
## Separate edge tracker for game-run bookkeeping: _last_play_state only
|
||||
## advances when the play_state_changed event sends successfully, but ending
|
||||
## run tracking must not depend on the websocket being up.
|
||||
var _last_play_state_for_run := false
|
||||
var _last_readiness := ""
|
||||
|
||||
|
||||
@@ -359,7 +376,22 @@ func _check_state_changes() -> void:
|
||||
if send_event("readiness_changed", {"readiness": readiness}):
|
||||
_last_readiness = readiness
|
||||
if log_buffer:
|
||||
log_buffer.log("[event] readiness -> %s" % readiness)
|
||||
## echo=false: readiness flips on every filesystem scan
|
||||
## (each import cycles importing -> ready), so echoing to
|
||||
## console spams every install during normal editing (#626).
|
||||
## The line stays in the ring for the dock's log panel.
|
||||
log_buffer.log("[event] readiness -> %s" % readiness, false)
|
||||
|
||||
|
||||
## Playing→stopped edge for game-run bookkeeping. Runs every process tick
|
||||
## (any socket state) so a self-quit game's run ends even while the
|
||||
## transport is down or reconnecting.
|
||||
func _check_game_run_play_state(playing: bool) -> void:
|
||||
if playing == _last_play_state_for_run:
|
||||
return
|
||||
if not playing and debugger_plugin != null:
|
||||
debugger_plugin.note_editor_play_stopped()
|
||||
_last_play_state_for_run = playing
|
||||
|
||||
|
||||
func _get_current_scene_path() -> String:
|
||||
@@ -403,6 +435,7 @@ func _handle_outbound_backpressure(
|
||||
return false
|
||||
|
||||
var err_response := _make_backpressure_error(request_id, buffered_bytes, message_bytes)
|
||||
_stamp_error_watermark(err_response)
|
||||
var err_text := JSON.stringify(err_response)
|
||||
var err_bytes := err_text.to_utf8_buffer().size()
|
||||
if _would_exceed_outbound_backpressure(buffered_bytes, err_bytes):
|
||||
@@ -457,6 +490,10 @@ static func _make_backpressure_error(
|
||||
}
|
||||
|
||||
|
||||
func _stamp_error_watermark(response: Dictionary) -> void:
|
||||
McpSurfacedErrorTracker.stamp_watermark(response, surfaced_error_tracker)
|
||||
|
||||
|
||||
## Build a human-readable session ID of form "<slug>@<4hex>" from the project path.
|
||||
## The slug is derived from the project directory name so agents can recognize
|
||||
## which editor they're targeting; the hex suffix disambiguates same-project twins.
|
||||
|
||||
Reference in New Issue
Block a user