fix(client): batch-encode inputs as Vec<PlayerInput> per server wire format
Server expects a MessagePack array of PlayerInput objects in one framed message per tick, not individual inputs per frame. Added Protocol.encode_player_inputs() for batch encoding. Changed SimBridge to buffer raw input dicts and batch-encode in _process(). Also fixed server port default (9876) and positional arg format to match server CLI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,12 +7,12 @@ var state: ConnectionState = ConnectionState.DISCONNECTED
|
||||
var test_mode: bool = true # Enable test mode for development without Rust server
|
||||
var _test_tick: int = 0
|
||||
var _last_snapshot: Variant = null # Most recent decoded snapshot (consumed by poll_snapshot)
|
||||
var _outbound_buffer: Array[PackedByteArray] = [] # Encoded inputs awaiting transport
|
||||
var _outbound_buffer: Array[Dictionary] = [] # Raw inputs awaiting batch encode + transport
|
||||
|
||||
# Transport layer (non-test mode)
|
||||
var _bridge: LocalBridge = null
|
||||
var _server: ServerProcess = null
|
||||
var server_port: int = 9800
|
||||
var server_port: int = 9876 # Default matches server's default bind address
|
||||
var server_path: String = "" # Path to server binary — set before connect_to_sim()
|
||||
|
||||
# Connection retry state — handles server startup delay (Critical fix #1)
|
||||
@@ -50,7 +50,8 @@ func connect_to_sim() -> void:
|
||||
# Spawn server subprocess
|
||||
if not server_path.is_empty():
|
||||
_server = ServerProcess.new()
|
||||
var pid := _server.start(server_path, ["--port", str(server_port)])
|
||||
# Server reads first positional arg as bind address (e.g. "127.0.0.1:9876")
|
||||
var pid := _server.start(server_path, ["127.0.0.1:" + str(server_port)])
|
||||
if pid <= 0:
|
||||
push_error("SimBridge: failed to start server")
|
||||
_set_state(ConnectionState.ERROR)
|
||||
@@ -129,12 +130,16 @@ func _process(delta: float) -> void:
|
||||
while msg.size() > 0:
|
||||
receive_bytes(msg)
|
||||
msg = _bridge.poll_message()
|
||||
# Send: flush outbound buffer through the bridge
|
||||
# Send: batch-encode and flush outbound buffer as one frame (Vec<PlayerInput>)
|
||||
var outbound := drain_outbound()
|
||||
for payload in outbound:
|
||||
var err := _bridge.send_message(payload)
|
||||
if err != OK:
|
||||
push_error("SimBridge: failed to send message: %s" % error_string(err))
|
||||
if outbound.size() > 0:
|
||||
var encoded := Protocol.encode_player_inputs(outbound)
|
||||
if encoded.size() > 0:
|
||||
var err := _bridge.send_message(encoded)
|
||||
if err != OK:
|
||||
push_error("SimBridge: failed to send message: %s" % error_string(err))
|
||||
else:
|
||||
push_error("SimBridge: failed to batch-encode %d inputs" % outbound.size())
|
||||
StreamPeerTCP.STATUS_CONNECTING:
|
||||
pass # Should not happen in CONNECTED state
|
||||
StreamPeerTCP.STATUS_ERROR:
|
||||
@@ -160,11 +165,12 @@ func send_input(player_input: Dictionary) -> Error:
|
||||
# _action_enum_to_wire already emits push_warning for invalid actions
|
||||
return ERR_INVALID_PARAMETER
|
||||
var tick: int = player_input.get("timestamp_msec", 0)
|
||||
var encoded := Protocol.encode_player_input(tick, action_name)
|
||||
if encoded.size() == 0:
|
||||
push_error("SimBridge: failed to encode player input (action=%s)" % action_name)
|
||||
return ERR_CANT_CREATE
|
||||
_outbound_buffer.append(encoded)
|
||||
var entry: Dictionary = { "tick": tick, "action_name": action_name }
|
||||
# Data variants (e.g. UsePerceptionMode) carry payload
|
||||
var action_data: Variant = player_input.get("action_data")
|
||||
if action_data != null:
|
||||
entry["action_data"] = action_data
|
||||
_outbound_buffer.append(entry)
|
||||
return OK
|
||||
|
||||
# Poll for snapshot from simulation.
|
||||
@@ -196,11 +202,11 @@ func receive_bytes(bytes: PackedByteArray) -> void:
|
||||
push_warning("SimBridge: overwriting unconsumed snapshot (tick %s replaced by %s)" % [_last_snapshot.tick, snapshot.tick])
|
||||
_last_snapshot = snapshot
|
||||
|
||||
# Drain the outbound buffer. Returns encoded messages for transport.
|
||||
func drain_outbound() -> Array[PackedByteArray]:
|
||||
var messages = _outbound_buffer.duplicate()
|
||||
# Drain the outbound buffer. Returns raw input entries for batch encoding.
|
||||
func drain_outbound() -> Array[Dictionary]:
|
||||
var inputs = _outbound_buffer.duplicate()
|
||||
_outbound_buffer.clear()
|
||||
return messages
|
||||
return inputs
|
||||
|
||||
# Map InputMapper.Action enum values to wire-format action names (matching Rust PlayerAction).
|
||||
# OPEN_MENU is client-only — no Rust equivalent, not sent over the wire.
|
||||
|
||||
@@ -107,6 +107,32 @@ static func encode_player_input(tick: int, action_name: String, action_data: Var
|
||||
return result.value
|
||||
|
||||
|
||||
## Encode an array of PlayerInputs to MessagePack bytes (Vec<PlayerInput> wire format).
|
||||
## Server expects one framed message per tick containing all inputs as a msgpack array.
|
||||
## Each entry: { "tick": int, "action_name": String, "action_data": Variant (optional) }
|
||||
static func encode_player_inputs(inputs: Array) -> PackedByteArray:
|
||||
var wire_inputs: Array = []
|
||||
for input in inputs:
|
||||
var action_name: String = input["action_name"]
|
||||
var action_data: Variant = input.get("action_data")
|
||||
var action_value: Variant
|
||||
if action_data != null:
|
||||
action_value = { action_name: action_data }
|
||||
else:
|
||||
action_value = action_name
|
||||
wire_inputs.append({
|
||||
"tick": input["tick"],
|
||||
"action": action_value,
|
||||
})
|
||||
|
||||
var result = Messagepack.encode(wire_inputs)
|
||||
if result.status != null:
|
||||
push_error("Protocol: msgpack encode failed: %s" % result.status)
|
||||
return PackedByteArray()
|
||||
|
||||
return result.value
|
||||
|
||||
|
||||
## Decode a PlayerInput from MessagePack bytes (used in tests / echo scenarios).
|
||||
## Returns { "tick": int, "action": { "variant": String, "data": Variant } } or null.
|
||||
static func decode_player_input(bytes: PackedByteArray) -> Variant:
|
||||
|
||||
Reference in New Issue
Block a user