gdharness 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js CHANGED
@@ -7153,10 +7153,10 @@ function runLine(runner, version, rest = "") {
7153
7153
  function spawnFor(runner, version, execPath = process2.execPath) {
7154
7154
  const spec = `gdharness@${version}`;
7155
7155
  if (runner !== currentRunner()) {
7156
- return { command: runner, args: runner === "npx" ? ["-y", spec] : [spec] };
7156
+ return { command: runner, args: runner === "npx" ? ["-y", spec] : ["--bun", spec] };
7157
7157
  }
7158
7158
  if (runner === "bunx") {
7159
- return { command: execPath, args: ["x", spec] };
7159
+ return { command: execPath, args: ["x", "--bun", spec] };
7160
7160
  }
7161
7161
  return { command: alongside(execPath, "npx") ?? "npx", args: ["-y", spec] };
7162
7162
  }
@@ -37,6 +37,14 @@ var _server: TCPServer
37
37
  var _clients: Array[StreamPeerTCP] = []
38
38
  ## Bytes received from each client that do not yet end in a newline, keyed by the peer.
39
39
  var _pending: Dictionary = {}
40
+ ## Bytes owed to each client that the socket has not taken yet, keyed by the peer.
41
+ ##
42
+ ## A reply is queued rather than written, because the write happens on the main thread and
43
+ ## `put_data` blocks until every byte is gone. A client that asked a large question and then
44
+ ## stopped reading, which is exactly what our own server does when a call times out, leaves that
45
+ ## write with nowhere to go: the frame never ends, the game freezes, and the listener never
46
+ ## accepts again, so every later request times out too. One timeout would take the game with it.
47
+ var _outgoing: Dictionary = {}
40
48
  var _port: int = 0
41
49
  var _enabled: bool = true
42
50
  var _announcement: String = ""
@@ -102,10 +110,27 @@ func _process(_delta: float) -> void:
102
110
  var received: Array = client.get_data(available)
103
111
  var bytes: PackedByteArray = received[1]
104
112
  _receive(client, bytes)
113
+ if not _drain(client):
114
+ gone.append(client)
105
115
 
106
116
  for client: StreamPeerTCP in gone:
107
117
  _clients.erase(client)
108
118
  _pending.erase(client)
119
+ _outgoing.erase(client)
120
+
121
+
122
+ ## Hands the socket as much of what it is owed as it will take, and says whether it is still worth
123
+ ## talking to. Reading comes first in the frame, so a client that asked and left is noticed here
124
+ ## rather than blocking on a write that can never finish.
125
+ func _drain(client: StreamPeerTCP) -> bool:
126
+ var owed: PackedByteArray = _outgoing.get(client, PackedByteArray())
127
+ if owed.is_empty():
128
+ return true
129
+ var sent: Array = client.put_partial_data(owed)
130
+ if sent[0] != OK:
131
+ return false
132
+ _outgoing[client] = owed.slice(int(sent[1]))
133
+ return true
109
134
 
110
135
 
111
136
  ## Bytes arrive in whatever pieces the socket makes of them, so a request is only handled once
@@ -259,10 +284,15 @@ func _ping(_params: Dictionary) -> Dictionary:
259
284
  return {"type": "pong", "timestamp": Time.get_unix_time_from_system()}
260
285
 
261
286
 
262
- ## One JSON object and a newline. put_data rather than put_utf8_string, which would prefix the
263
- ## bytes with a length the other side is not expecting.
287
+ ## One JSON object and a newline, queued for [method _drain] rather than written here.
288
+ ##
289
+ ## Raw bytes rather than put_utf8_string, which would prefix them with a length the other side is
290
+ ## not expecting, and queued rather than put_data, which blocks the frame until the socket has
291
+ ## taken the lot: see [member _outgoing].
264
292
  func _send_response(client: StreamPeerTCP, data: Dictionary) -> void:
265
- client.put_data((JSON.stringify(data) + "\n").to_utf8_buffer())
293
+ var owed: PackedByteArray = _outgoing.get(client, PackedByteArray())
294
+ owed.append_array((JSON.stringify(data) + "\n").to_utf8_buffer())
295
+ _outgoing[client] = owed
266
296
 
267
297
 
268
298
  ## A request that could not be read far enough to find its id is answered with a null one.
@@ -280,6 +310,7 @@ func _cleanup() -> void:
280
310
  client.disconnect_from_host()
281
311
  _clients.clear()
282
312
  _pending.clear()
313
+ _outgoing.clear()
283
314
 
284
315
  if _server:
285
316
  _server.stop()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",