gdharness 0.9.2 → 0.9.3
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
|
@@ -173,6 +173,7 @@ function userDataIn(home, variables = process.env) {
|
|
|
173
173
|
}
|
|
174
174
|
return carried;
|
|
175
175
|
}
|
|
176
|
+
var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER";
|
|
176
177
|
|
|
177
178
|
// node_modules/yaml/dist/nodes/identity.js
|
|
178
179
|
var require_identity = __commonJS(function(exports) {
|
|
@@ -34499,6 +34500,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
34499
34500
|
lspPort: this.connectionInfo?.lspPort,
|
|
34500
34501
|
dapPort: this.connectionInfo?.dapPort,
|
|
34501
34502
|
debugPort: this.connectionInfo?.debugPort,
|
|
34503
|
+
openedByAServer: this.connectionInfo?.openedByAServer,
|
|
34502
34504
|
pendingRequests: this.pendingRequests.size,
|
|
34503
34505
|
queuedResources: this.resourceQueues.size
|
|
34504
34506
|
};
|
|
@@ -34639,6 +34641,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
34639
34641
|
this.connectionInfo.lspPort = servedPort(message.lsp_port);
|
|
34640
34642
|
this.connectionInfo.dapPort = servedPort(message.dap_port);
|
|
34641
34643
|
this.connectionInfo.debugPort = servedPort(message.debug_port);
|
|
34644
|
+
this.connectionInfo.openedByAServer = message.opened_by_a_server === true;
|
|
34642
34645
|
this.log("info", `Godot ready: ${message.project_path}`);
|
|
34643
34646
|
this.emitBridgeEvent("godot_connected", { projectPath: message.project_path });
|
|
34644
34647
|
}
|
|
@@ -35999,6 +36002,17 @@ import { promisify as promisify5 } from "node:util";
|
|
|
35999
36002
|
function patienceForFrames(frames, atLeast) {
|
|
36000
36003
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
36001
36004
|
}
|
|
36005
|
+
function alive(pid) {
|
|
36006
|
+
if (pid === undefined) {
|
|
36007
|
+
return false;
|
|
36008
|
+
}
|
|
36009
|
+
try {
|
|
36010
|
+
process.kill(pid, 0);
|
|
36011
|
+
return true;
|
|
36012
|
+
} catch {
|
|
36013
|
+
return false;
|
|
36014
|
+
}
|
|
36015
|
+
}
|
|
36002
36016
|
function isOfType(value, kind) {
|
|
36003
36017
|
switch (kind) {
|
|
36004
36018
|
case "string":
|
|
@@ -36234,12 +36248,7 @@ class GodotServer {
|
|
|
36234
36248
|
if (announced === null || announced.pid === process.pid) {
|
|
36235
36249
|
return null;
|
|
36236
36250
|
}
|
|
36237
|
-
|
|
36238
|
-
process.kill(announced.pid, 0);
|
|
36239
|
-
return announced.pid;
|
|
36240
|
-
} catch {
|
|
36241
|
-
return null;
|
|
36242
|
-
}
|
|
36251
|
+
return alive(announced.pid) ? announced.pid : null;
|
|
36243
36252
|
}
|
|
36244
36253
|
stopWatchingForASuccessor() {
|
|
36245
36254
|
if (this.successorWatch !== null) {
|
|
@@ -37206,7 +37215,8 @@ class GodotServer {
|
|
|
37206
37215
|
"editor_status says whether the bridge is up and what has reached it"
|
|
37207
37216
|
]);
|
|
37208
37217
|
}
|
|
37209
|
-
const
|
|
37218
|
+
const mine = before.openedByAServer === true && before.projectPath !== undefined;
|
|
37219
|
+
const asked = mine ? await this.startItAgain(before.projectPath ?? "", before.editorPid) : await this.handleViaBridge("restart_editor", {});
|
|
37210
37220
|
if (asked.isError === true) {
|
|
37211
37221
|
return asked;
|
|
37212
37222
|
}
|
|
@@ -37234,6 +37244,26 @@ class GodotServer {
|
|
|
37234
37244
|
tookMs: Date.now() - began
|
|
37235
37245
|
});
|
|
37236
37246
|
}
|
|
37247
|
+
async startItAgain(projectPath, editorPid) {
|
|
37248
|
+
const engine = await this.engine();
|
|
37249
|
+
if (!engine.ok) {
|
|
37250
|
+
return engine.response;
|
|
37251
|
+
}
|
|
37252
|
+
const ports = {
|
|
37253
|
+
lsp: this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT),
|
|
37254
|
+
dap: this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT)
|
|
37255
|
+
};
|
|
37256
|
+
const asked = await this.handleViaBridge("quit_editor", {});
|
|
37257
|
+
if (asked.isError === true) {
|
|
37258
|
+
return asked;
|
|
37259
|
+
}
|
|
37260
|
+
await this.waitForBridge(() => !alive(editorPid), Date.now() + EDITOR_RESTART_TIMEOUT_MS);
|
|
37261
|
+
const opened = await this.openAnEditor(engine.value, projectPath, ports);
|
|
37262
|
+
if (opened.error !== null) {
|
|
37263
|
+
return this.createErrorResponse(`The editor was asked to go and could not be started again: ${opened.error}`, [`editor_launch opens one on ${projectPath}`]);
|
|
37264
|
+
}
|
|
37265
|
+
return this.jsonTextResponse({ restarting: true, pid: opened.pid });
|
|
37266
|
+
}
|
|
37237
37267
|
async waitForBridge(reached, deadline) {
|
|
37238
37268
|
for (;; ) {
|
|
37239
37269
|
if (reached()) {
|
|
@@ -37263,14 +37293,28 @@ class GodotServer {
|
|
|
37263
37293
|
}
|
|
37264
37294
|
this.logDebug(`Launching Godot editor for project: ${project.value.path}`);
|
|
37265
37295
|
const ports = await this.portsForAnEditor();
|
|
37266
|
-
const
|
|
37296
|
+
const opened = await this.openAnEditor(engine.value, project.value.path, ports);
|
|
37297
|
+
if (opened.error !== null) {
|
|
37298
|
+
return this.createErrorResponse(`Could not start the editor: ${opened.error}`);
|
|
37299
|
+
}
|
|
37300
|
+
return this.jsonTextResponse({
|
|
37301
|
+
launched: true,
|
|
37302
|
+
pid: opened.pid,
|
|
37303
|
+
projectPath: project.value.path,
|
|
37304
|
+
lspPort: ports.lsp,
|
|
37305
|
+
dapPort: ports.dap
|
|
37306
|
+
});
|
|
37307
|
+
}
|
|
37308
|
+
async openAnEditor(engine, projectPath, ports) {
|
|
37309
|
+
const editor = spawn(engine, editorArguments(projectPath, ports), {
|
|
37267
37310
|
stdio: "ignore",
|
|
37268
37311
|
detached: true,
|
|
37269
37312
|
env: {
|
|
37270
37313
|
...process.env,
|
|
37271
37314
|
GDHARNESS_RUNTIME_DIR: runtimeDirectory(),
|
|
37272
37315
|
GDHARNESS_LSP_PORT: String(ports.lsp),
|
|
37273
|
-
GDHARNESS_DAP_PORT: String(ports.dap)
|
|
37316
|
+
GDHARNESS_DAP_PORT: String(ports.dap),
|
|
37317
|
+
[OPENED_BY_A_SERVER]: "1"
|
|
37274
37318
|
}
|
|
37275
37319
|
});
|
|
37276
37320
|
const started = await new Promise((resolve) => {
|
|
@@ -37282,16 +37326,10 @@ class GodotServer {
|
|
|
37282
37326
|
});
|
|
37283
37327
|
});
|
|
37284
37328
|
if (started !== null) {
|
|
37285
|
-
return
|
|
37329
|
+
return { pid: null, error: started };
|
|
37286
37330
|
}
|
|
37287
37331
|
editor.unref();
|
|
37288
|
-
return
|
|
37289
|
-
launched: true,
|
|
37290
|
-
pid: editor.pid ?? null,
|
|
37291
|
-
projectPath: project.value.path,
|
|
37292
|
-
lspPort: ports.lsp,
|
|
37293
|
-
dapPort: ports.dap
|
|
37294
|
-
});
|
|
37332
|
+
return { pid: editor.pid ?? null, error: null };
|
|
37295
37333
|
}
|
|
37296
37334
|
async portsForAnEditor() {
|
|
37297
37335
|
const lsp = portFromEnvOrNull("GDHARNESS_LSP_PORT") ?? await freePort(DEFAULT_LSP_PORT);
|
|
@@ -38412,27 +38450,42 @@ function displayPath(harness, platform = process3.platform) {
|
|
|
38412
38450
|
}
|
|
38413
38451
|
return `${harness.scope === "home" ? "~/" : ""}${file.replaceAll("\\", "/")}`;
|
|
38414
38452
|
}
|
|
38415
|
-
|
|
38453
|
+
var OURS = ["GODOT_PATH", "GDHARNESS_PROJECT"];
|
|
38454
|
+
function environmentFor(launch, held) {
|
|
38455
|
+
const kept = {};
|
|
38456
|
+
if (typeof held === "object" && held !== null && !Array.isArray(held)) {
|
|
38457
|
+
for (const [name, value] of Object.entries(held)) {
|
|
38458
|
+
if (typeof value === "string" && !OURS.includes(name)) {
|
|
38459
|
+
kept[name] = value;
|
|
38460
|
+
}
|
|
38461
|
+
}
|
|
38462
|
+
}
|
|
38463
|
+
return { ...kept, ...launch.env };
|
|
38464
|
+
}
|
|
38465
|
+
function entryFor(shape, launch, held) {
|
|
38466
|
+
const before = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
|
|
38416
38467
|
if (shape === "opencode") {
|
|
38417
38468
|
return {
|
|
38469
|
+
...before,
|
|
38418
38470
|
type: "local",
|
|
38419
38471
|
command: [launch.command, ...launch.args],
|
|
38420
|
-
environment:
|
|
38472
|
+
environment: environmentFor(launch, before["environment"]),
|
|
38421
38473
|
enabled: true
|
|
38422
38474
|
};
|
|
38423
38475
|
}
|
|
38424
38476
|
return {
|
|
38477
|
+
...before,
|
|
38425
38478
|
...shape === "typed" ? { type: "stdio" } : {},
|
|
38426
38479
|
command: launch.command,
|
|
38427
38480
|
args: [...launch.args],
|
|
38428
|
-
env:
|
|
38481
|
+
env: environmentFor(launch, before["env"])
|
|
38429
38482
|
};
|
|
38430
38483
|
}
|
|
38431
38484
|
function merged(existing, harness, launch) {
|
|
38432
38485
|
const root = typeof existing === "object" && existing !== null && !Array.isArray(existing) ? { ...existing } : {};
|
|
38433
38486
|
const held = root[harness.container];
|
|
38434
38487
|
const servers = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
|
|
38435
|
-
servers[SERVER_KEY] = entryFor(harness.shape, launch);
|
|
38488
|
+
servers[SERVER_KEY] = entryFor(harness.shape, launch, servers[SERVER_KEY]);
|
|
38436
38489
|
root[harness.container] = servers;
|
|
38437
38490
|
return root;
|
|
38438
38491
|
}
|
|
@@ -23,8 +23,9 @@ const MAX_RECONNECT_DELAY: float = 30.0
|
|
|
23
23
|
const ELSEWHERE_CLOSE_CODE: int = 4001
|
|
24
24
|
|
|
25
25
|
## Where Godot keeps the three ports an editor serves. None of them is per editor: the settings
|
|
26
|
-
## file is one for every editor on the machine, so two open at once want the
|
|
27
|
-
## second one binds nothing.
|
|
26
|
+
## file is one for every editor of this engine version on the machine, so two open at once want the
|
|
27
|
+
## same three and the second one binds nothing. Read here and never written, for the reason
|
|
28
|
+
## [method _serves] gives.
|
|
28
29
|
const LSP_SETTING: String = "network/language_server/remote_port"
|
|
29
30
|
const DAP_SETTING: String = "network/debug_adapter/remote_port"
|
|
30
31
|
const DEBUGGER_SETTING: String = "network/debug/remote_port"
|
|
@@ -32,6 +33,9 @@ const DEBUGGER_SETTING: String = "network/debug/remote_port"
|
|
|
32
33
|
## the command line. Kept in step with `editorArguments` in src/launch.ts.
|
|
33
34
|
const LSP_ASKED: String = "GDHARNESS_LSP_PORT"
|
|
34
35
|
const DAP_ASKED: String = "GDHARNESS_DAP_PORT"
|
|
36
|
+
## What a server that opened this editor says about itself, so that being opened by one is stated
|
|
37
|
+
## rather than guessed at from the ports. Kept in step with `OPENED_BY_A_SERVER` in src/launch.ts.
|
|
38
|
+
const OPENED_BY_A_SERVER: String = "GDHARNESS_OPENED_BY_A_SERVER"
|
|
35
39
|
|
|
36
40
|
## How long one attempt is given before the address is called a bad one.
|
|
37
41
|
##
|
|
@@ -81,7 +85,6 @@ var _connecting_for: float = 0.0
|
|
|
81
85
|
func _ready() -> void:
|
|
82
86
|
_project_path = ProjectSettings.globalize_path("res://")
|
|
83
87
|
version_at_load = _loaded_version()
|
|
84
|
-
_keep_the_ports_this_editor_was_given()
|
|
85
88
|
|
|
86
89
|
_reconnect_timer = Timer.new()
|
|
87
90
|
_reconnect_timer.one_shot = true
|
|
@@ -273,39 +276,41 @@ func _handle_connect() -> void:
|
|
|
273
276
|
"editor_pid": OS.get_process_id(),
|
|
274
277
|
"lsp_port": _serves(LSP_ASKED, LSP_SETTING),
|
|
275
278
|
"dap_port": _serves(DAP_ASKED, DAP_SETTING),
|
|
276
|
-
"debug_port": _serving(DEBUGGER_SETTING)
|
|
279
|
+
"debug_port": _serving(DEBUGGER_SETTING),
|
|
280
|
+
# Whether a server started this editor, which is the only kind that can be started again
|
|
281
|
+
# as itself: the engine hands back none of the arguments it was given, so the arguments
|
|
282
|
+
# have to come from whoever wrote them. See `restart_editor` in play_tools.gd.
|
|
283
|
+
"opened_by_a_server": _opened_by_a_server()
|
|
277
284
|
}
|
|
278
285
|
)
|
|
279
286
|
|
|
280
287
|
connected.emit()
|
|
281
288
|
|
|
282
289
|
|
|
283
|
-
##
|
|
290
|
+
## Whether a gdharness server started this editor, which it says in the environment.
|
|
284
291
|
##
|
|
285
|
-
##
|
|
286
|
-
##
|
|
287
|
-
##
|
|
288
|
-
##
|
|
289
|
-
##
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
_keep_port(DAP_ASKED, DAP_SETTING)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
func _keep_port(variable: String, setting: String) -> void:
|
|
298
|
-
var port: int = _asked_for(variable)
|
|
299
|
-
if port < 1 or port == _serving(setting):
|
|
300
|
-
return
|
|
301
|
-
var settings: EditorSettings = EditorInterface.get_editor_settings()
|
|
302
|
-
if settings != null:
|
|
303
|
-
settings.set_setting(setting, port)
|
|
292
|
+
## It decides who may restart this editor by starting it again. Only a server that wrote the
|
|
293
|
+
## arguments can write them a second time, and only an editor started that way needs it: one opened
|
|
294
|
+
## by hand is on the ports its own settings name and comes back on them by itself.
|
|
295
|
+
##
|
|
296
|
+
## Said outright rather than read off the two port variables, which a server also sets and anybody
|
|
297
|
+
## else may: an editor started from a shell that exports them is one somebody opened themselves,
|
|
298
|
+
## and taking it for a server's is taking an editor off somebody's desk and putting a new one there.
|
|
299
|
+
static func _opened_by_a_server() -> bool:
|
|
300
|
+
return not OS.get_environment(OPENED_BY_A_SERVER).is_empty()
|
|
304
301
|
|
|
305
302
|
|
|
306
|
-
## What this editor serves: what it was told to when a server opened it, and what its settings
|
|
307
|
-
##
|
|
308
|
-
##
|
|
303
|
+
## What this editor serves: what it was told to when a server opened it, and what its settings say
|
|
304
|
+
## otherwise. The first is the one that counts, because the settings are what the command line was
|
|
305
|
+
## overriding.
|
|
306
|
+
##
|
|
307
|
+
## The settings are read and never written. They were written for a while, because the engine keeps
|
|
308
|
+
## a command-line port override to itself and hands none of it back, so an editor restarting itself
|
|
309
|
+
## came up on whatever the settings said. But there is one settings file for every editor of this
|
|
310
|
+
## engine version on the machine, so writing a port chosen for one project made it the number every
|
|
311
|
+
## other project's editor came up on: an editor opened by hand for something else inherited it and
|
|
312
|
+
## collided with the editor it was moved away from. Restarting is done by starting the editor again
|
|
313
|
+
## with the same arguments now, which is `editor_launch restart`, so there is nothing to persist.
|
|
309
314
|
func _serves(variable: String, setting: String) -> int:
|
|
310
315
|
var asked: int = _asked_for(variable)
|
|
311
316
|
return asked if asked > 0 else _serving(setting)
|
|
@@ -77,6 +77,7 @@ func _init_tools() -> void:
|
|
|
77
77
|
# Animation tools
|
|
78
78
|
"play_scene": [_play_tools, "play_scene"],
|
|
79
79
|
"restart_editor": [_play_tools, "restart_editor"],
|
|
80
|
+
"quit_editor": [_play_tools, "quit_editor"],
|
|
80
81
|
"stop_playing": [_play_tools, "stop_playing"],
|
|
81
82
|
"playing_status": [_play_tools, "playing_status"],
|
|
82
83
|
"create_animation": [_animation_tools, "create_animation"],
|
|
@@ -125,6 +125,33 @@ func restart_editor(_args: Dictionary) -> Dictionary:
|
|
|
125
125
|
return {"ok": true, "restarting": true, "saved": true}
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
## Ends this editor, having saved, so that whoever opened it can open it again.
|
|
129
|
+
##
|
|
130
|
+
## The editor's half of restarting one a server started. Godot's own restart cannot carry the ports
|
|
131
|
+
## such an editor was given, for the reason above: the engine consumes the arguments and hands none
|
|
132
|
+
## of them back. So the server starts it again itself, and this is the part only the editor can do.
|
|
133
|
+
##
|
|
134
|
+
## Saved first, exactly as the restart above saves. The editor is going either way, and unsaved
|
|
135
|
+
## scenes are not this tool's to lose.
|
|
136
|
+
func quit_editor(_args: Dictionary) -> Dictionary:
|
|
137
|
+
if EditorInterface.is_playing_scene():
|
|
138
|
+
EditorInterface.stop_playing_scene()
|
|
139
|
+
|
|
140
|
+
EditorInterface.save_all_scenes()
|
|
141
|
+
|
|
142
|
+
# Deferred so this answer is on its way out before the editor goes.
|
|
143
|
+
_leave.call_deferred()
|
|
144
|
+
|
|
145
|
+
return {"ok": true, "quitting": true, "saved": true}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
## Asked of the tree the editor's own window is in, because this object is not in one.
|
|
149
|
+
static func _leave() -> void:
|
|
150
|
+
var base: Control = EditorInterface.get_base_control()
|
|
151
|
+
if base != null and base.get_tree() != null:
|
|
152
|
+
base.get_tree().quit()
|
|
153
|
+
|
|
154
|
+
|
|
128
155
|
func playing_status(_args: Dictionary) -> Dictionary:
|
|
129
156
|
var playing: bool = EditorInterface.is_playing_scene()
|
|
130
157
|
# The debugger's port with it, read now rather than remembered: it is taken again before every
|
package/build/index.js
CHANGED
|
@@ -25718,6 +25718,7 @@ class GodotBridge extends EventEmitter {
|
|
|
25718
25718
|
lspPort: this.connectionInfo?.lspPort,
|
|
25719
25719
|
dapPort: this.connectionInfo?.dapPort,
|
|
25720
25720
|
debugPort: this.connectionInfo?.debugPort,
|
|
25721
|
+
openedByAServer: this.connectionInfo?.openedByAServer,
|
|
25721
25722
|
pendingRequests: this.pendingRequests.size,
|
|
25722
25723
|
queuedResources: this.resourceQueues.size
|
|
25723
25724
|
};
|
|
@@ -25858,6 +25859,7 @@ class GodotBridge extends EventEmitter {
|
|
|
25858
25859
|
this.connectionInfo.lspPort = servedPort(message.lsp_port);
|
|
25859
25860
|
this.connectionInfo.dapPort = servedPort(message.dap_port);
|
|
25860
25861
|
this.connectionInfo.debugPort = servedPort(message.debug_port);
|
|
25862
|
+
this.connectionInfo.openedByAServer = message.opened_by_a_server === true;
|
|
25861
25863
|
this.log("info", `Godot ready: ${message.project_path}`);
|
|
25862
25864
|
this.emitBridgeEvent("godot_connected", { projectPath: message.project_path });
|
|
25863
25865
|
}
|
|
@@ -26148,6 +26150,7 @@ function runArguments(options) {
|
|
|
26148
26150
|
function editorArguments(projectPath, ports) {
|
|
26149
26151
|
return ["-e", "--path", projectPath, "--lsp-port", String(ports.lsp), "--dap-port", String(ports.dap)];
|
|
26150
26152
|
}
|
|
26153
|
+
var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER";
|
|
26151
26154
|
function userDataIn(home, variables = process.env) {
|
|
26152
26155
|
const moved = ["APPDATA", "XDG_DATA_HOME"];
|
|
26153
26156
|
const carried = {};
|
|
@@ -28717,6 +28720,17 @@ var SLOWEST_FRAME_RATE = 20;
|
|
|
28717
28720
|
function patienceForFrames(frames, atLeast) {
|
|
28718
28721
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
28719
28722
|
}
|
|
28723
|
+
function alive(pid) {
|
|
28724
|
+
if (pid === undefined) {
|
|
28725
|
+
return false;
|
|
28726
|
+
}
|
|
28727
|
+
try {
|
|
28728
|
+
process.kill(pid, 0);
|
|
28729
|
+
return true;
|
|
28730
|
+
} catch {
|
|
28731
|
+
return false;
|
|
28732
|
+
}
|
|
28733
|
+
}
|
|
28720
28734
|
function isOfType(value, kind) {
|
|
28721
28735
|
switch (kind) {
|
|
28722
28736
|
case "string":
|
|
@@ -29014,12 +29028,7 @@ class GodotServer {
|
|
|
29014
29028
|
if (announced === null || announced.pid === process.pid) {
|
|
29015
29029
|
return null;
|
|
29016
29030
|
}
|
|
29017
|
-
|
|
29018
|
-
process.kill(announced.pid, 0);
|
|
29019
|
-
return announced.pid;
|
|
29020
|
-
} catch {
|
|
29021
|
-
return null;
|
|
29022
|
-
}
|
|
29031
|
+
return alive(announced.pid) ? announced.pid : null;
|
|
29023
29032
|
}
|
|
29024
29033
|
stopWatchingForASuccessor() {
|
|
29025
29034
|
if (this.successorWatch !== null) {
|
|
@@ -29986,7 +29995,8 @@ class GodotServer {
|
|
|
29986
29995
|
"editor_status says whether the bridge is up and what has reached it"
|
|
29987
29996
|
]);
|
|
29988
29997
|
}
|
|
29989
|
-
const
|
|
29998
|
+
const mine = before.openedByAServer === true && before.projectPath !== undefined;
|
|
29999
|
+
const asked = mine ? await this.startItAgain(before.projectPath ?? "", before.editorPid) : await this.handleViaBridge("restart_editor", {});
|
|
29990
30000
|
if (asked.isError === true) {
|
|
29991
30001
|
return asked;
|
|
29992
30002
|
}
|
|
@@ -30014,6 +30024,26 @@ class GodotServer {
|
|
|
30014
30024
|
tookMs: Date.now() - began
|
|
30015
30025
|
});
|
|
30016
30026
|
}
|
|
30027
|
+
async startItAgain(projectPath, editorPid) {
|
|
30028
|
+
const engine = await this.engine();
|
|
30029
|
+
if (!engine.ok) {
|
|
30030
|
+
return engine.response;
|
|
30031
|
+
}
|
|
30032
|
+
const ports = {
|
|
30033
|
+
lsp: this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT),
|
|
30034
|
+
dap: this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT)
|
|
30035
|
+
};
|
|
30036
|
+
const asked = await this.handleViaBridge("quit_editor", {});
|
|
30037
|
+
if (asked.isError === true) {
|
|
30038
|
+
return asked;
|
|
30039
|
+
}
|
|
30040
|
+
await this.waitForBridge(() => !alive(editorPid), Date.now() + EDITOR_RESTART_TIMEOUT_MS);
|
|
30041
|
+
const opened = await this.openAnEditor(engine.value, projectPath, ports);
|
|
30042
|
+
if (opened.error !== null) {
|
|
30043
|
+
return this.createErrorResponse(`The editor was asked to go and could not be started again: ${opened.error}`, [`editor_launch opens one on ${projectPath}`]);
|
|
30044
|
+
}
|
|
30045
|
+
return this.jsonTextResponse({ restarting: true, pid: opened.pid });
|
|
30046
|
+
}
|
|
30017
30047
|
async waitForBridge(reached, deadline) {
|
|
30018
30048
|
for (;; ) {
|
|
30019
30049
|
if (reached()) {
|
|
@@ -30043,14 +30073,28 @@ class GodotServer {
|
|
|
30043
30073
|
}
|
|
30044
30074
|
this.logDebug(`Launching Godot editor for project: ${project.value.path}`);
|
|
30045
30075
|
const ports = await this.portsForAnEditor();
|
|
30046
|
-
const
|
|
30076
|
+
const opened = await this.openAnEditor(engine.value, project.value.path, ports);
|
|
30077
|
+
if (opened.error !== null) {
|
|
30078
|
+
return this.createErrorResponse(`Could not start the editor: ${opened.error}`);
|
|
30079
|
+
}
|
|
30080
|
+
return this.jsonTextResponse({
|
|
30081
|
+
launched: true,
|
|
30082
|
+
pid: opened.pid,
|
|
30083
|
+
projectPath: project.value.path,
|
|
30084
|
+
lspPort: ports.lsp,
|
|
30085
|
+
dapPort: ports.dap
|
|
30086
|
+
});
|
|
30087
|
+
}
|
|
30088
|
+
async openAnEditor(engine, projectPath, ports) {
|
|
30089
|
+
const editor = spawn(engine, editorArguments(projectPath, ports), {
|
|
30047
30090
|
stdio: "ignore",
|
|
30048
30091
|
detached: true,
|
|
30049
30092
|
env: {
|
|
30050
30093
|
...process.env,
|
|
30051
30094
|
GDHARNESS_RUNTIME_DIR: runtimeDirectory(),
|
|
30052
30095
|
GDHARNESS_LSP_PORT: String(ports.lsp),
|
|
30053
|
-
GDHARNESS_DAP_PORT: String(ports.dap)
|
|
30096
|
+
GDHARNESS_DAP_PORT: String(ports.dap),
|
|
30097
|
+
[OPENED_BY_A_SERVER]: "1"
|
|
30054
30098
|
}
|
|
30055
30099
|
});
|
|
30056
30100
|
const started = await new Promise((resolve) => {
|
|
@@ -30062,16 +30106,10 @@ class GodotServer {
|
|
|
30062
30106
|
});
|
|
30063
30107
|
});
|
|
30064
30108
|
if (started !== null) {
|
|
30065
|
-
return
|
|
30109
|
+
return { pid: null, error: started };
|
|
30066
30110
|
}
|
|
30067
30111
|
editor.unref();
|
|
30068
|
-
return
|
|
30069
|
-
launched: true,
|
|
30070
|
-
pid: editor.pid ?? null,
|
|
30071
|
-
projectPath: project.value.path,
|
|
30072
|
-
lspPort: ports.lsp,
|
|
30073
|
-
dapPort: ports.dap
|
|
30074
|
-
});
|
|
30112
|
+
return { pid: editor.pid ?? null, error: null };
|
|
30075
30113
|
}
|
|
30076
30114
|
async portsForAnEditor() {
|
|
30077
30115
|
const lsp = portFromEnvOrNull("GDHARNESS_LSP_PORT") ?? await freePort(DEFAULT_LSP_PORT);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
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",
|