gdharness 1.1.3 → 1.1.5
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
|
@@ -114,11 +114,11 @@ function startTimesOf(pids) {
|
|
|
114
114
|
}
|
|
115
115
|
async function processTree() {
|
|
116
116
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
const tree = process.platform === "win32" ? parseProcessTable(await askWindows(), true) : parseProcessTable(await askPosix());
|
|
118
|
+
lastListingFailure = undefined;
|
|
119
|
+
return tree;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
lastListingFailure = error instanceof Error ? error.message : String(error);
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -256,7 +256,7 @@ async function askWindows() {
|
|
|
256
256
|
"-NoProfile",
|
|
257
257
|
"-NonInteractive",
|
|
258
258
|
"-Command",
|
|
259
|
-
`${POWERSHELL_UTF8}Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
|
|
259
|
+
`${POWERSHELL_UTF8}Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate,CommandLine | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
|
|
260
260
|
], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
261
261
|
return stdout;
|
|
262
262
|
}
|
|
@@ -267,7 +267,7 @@ async function askPosix() {
|
|
|
267
267
|
});
|
|
268
268
|
return stdout;
|
|
269
269
|
}
|
|
270
|
-
var run, POWERSHELL_UTF8 = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ", ASK_TIMEOUT_MS = 15000;
|
|
270
|
+
var run, POWERSHELL_UTF8 = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ", ASK_TIMEOUT_MS = 15000, lastListingFailure;
|
|
271
271
|
var init_process_children = __esm(() => {
|
|
272
272
|
run = promisify(execFile);
|
|
273
273
|
});
|
|
@@ -15992,7 +15992,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15992
15992
|
requires: ["projectPath"],
|
|
15993
15993
|
operations: {
|
|
15994
15994
|
status: {
|
|
15995
|
-
summary: "which resources are outdated or failed, or one resource with resourcePath: a source
|
|
15995
|
+
summary: "which resources are outdated or failed, or one resource with resourcePath, judged by content as the editor judges it: a source whose hash differs from the one its import recorded, an import whose output under .godot/imported is gone (missing_outputs), and a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without), each with its reason",
|
|
15996
15996
|
requires: []
|
|
15997
15997
|
},
|
|
15998
15998
|
options: { summary: "the import options of one resource", requires: ["resourcePath"] },
|
|
@@ -41334,7 +41334,10 @@ class GodotServer {
|
|
|
41334
41334
|
adopted.log.finish();
|
|
41335
41335
|
adopted.exitCode = record.exitCode ?? null;
|
|
41336
41336
|
adopted.exitSignal = record.exitSignal ?? null;
|
|
41337
|
-
|
|
41337
|
+
const exitMayBeComing = !alive(record.pid) && !exited(adopted);
|
|
41338
|
+
if (!exitMayBeComing) {
|
|
41339
|
+
adopted.endedUnwatched = !exited(adopted);
|
|
41340
|
+
}
|
|
41338
41341
|
}
|
|
41339
41342
|
this.activeProcess = adopted;
|
|
41340
41343
|
return adopted;
|
|
@@ -41824,6 +41827,7 @@ class GodotServer {
|
|
|
41824
41827
|
const ghosts = blind.stillHeld ?? [];
|
|
41825
41828
|
let waitedForEditorMs = 0;
|
|
41826
41829
|
let first;
|
|
41830
|
+
let completedBefore;
|
|
41827
41831
|
for (;; ) {
|
|
41828
41832
|
const busySince = Date.now();
|
|
41829
41833
|
let wasBusy = false;
|
|
@@ -41838,8 +41842,9 @@ class GodotServer {
|
|
|
41838
41842
|
if (first.isError) {
|
|
41839
41843
|
return first;
|
|
41840
41844
|
}
|
|
41841
|
-
const
|
|
41842
|
-
|
|
41845
|
+
const firstAnswer = asParams(JSON.parse(first.content[0]?.text ?? "{}"));
|
|
41846
|
+
completedBefore = readNumber(firstAnswer, "scansCompletedBefore");
|
|
41847
|
+
if (firstAnswer["started"] !== false || Date.now() - started >= timeoutMs) {
|
|
41843
41848
|
break;
|
|
41844
41849
|
}
|
|
41845
41850
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
@@ -41850,7 +41855,9 @@ class GodotServer {
|
|
|
41850
41855
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
41851
41856
|
const status = asParams(await this.godotBridge.invokeTool("rescan_filesystem", { ...args, statusOnly: true }));
|
|
41852
41857
|
const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
|
|
41853
|
-
|
|
41858
|
+
const completed = readNumber(status, "scansCompleted");
|
|
41859
|
+
const notYetOver = completedBefore !== undefined && completed !== undefined && completed <= completedBefore;
|
|
41860
|
+
busy = Boolean(status["scanning"]) || Boolean(status["importing"]) || pending || notYetOver;
|
|
41854
41861
|
if (busy) {
|
|
41855
41862
|
idleSince = null;
|
|
41856
41863
|
} else {
|
|
@@ -11,12 +11,14 @@ var _editor_plugin: EditorPlugin = null
|
|
|
11
11
|
var _values: Serialisation = Serialisation.new()
|
|
12
12
|
var _properties: PropertyValues = PropertyValues.new()
|
|
13
13
|
|
|
14
|
-
## How many
|
|
15
|
-
##
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
## How many scans the editor has completed, counted off `sources_changed`, which it emits at the end
|
|
15
|
+
## of every scan once the reimport the scan found is done: the one moment a scan is over. Neither
|
|
16
|
+
## flag it offers says so. A threaded scan stops reporting itself when its thread finishes, and its
|
|
17
|
+
## import runs a frame or more later when the thread is joined; an editor in the background took
|
|
18
|
+
## long enough over that frame that a rescan answered before the import of 211 changed scenes began.
|
|
19
|
+
var _scans_completed: int = 0
|
|
20
|
+
|
|
21
|
+
## When the last scan completed, on the engine's clock, or -1 before the first. The editor writes
|
|
20
22
|
## the class cache a frame after it stops reporting a scan, measured on 4.7.2, so a game started
|
|
21
23
|
## in between reads a file being rewritten; how long ago the scan ended is what tells a caller
|
|
22
24
|
## whether that write can still be coming.
|
|
@@ -26,14 +28,14 @@ var _scan_finished_at_msec: int = -1
|
|
|
26
28
|
func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
27
29
|
_editor_plugin = plugin
|
|
28
30
|
var filesystem: EditorFileSystem = EditorInterface.get_resource_filesystem()
|
|
29
|
-
if not filesystem.
|
|
30
|
-
var watched: int = filesystem.
|
|
31
|
+
if not filesystem.sources_changed.is_connected(_scan_completed):
|
|
32
|
+
var watched: int = filesystem.sources_changed.connect(_scan_completed)
|
|
31
33
|
if watched != OK:
|
|
32
|
-
push_error("gdharness could not watch for
|
|
34
|
+
push_error("gdharness could not watch for completed scans: %d" % watched)
|
|
33
35
|
|
|
34
36
|
|
|
35
|
-
func
|
|
36
|
-
|
|
37
|
+
func _scan_completed(_sources_exist: bool) -> void:
|
|
38
|
+
_scans_completed += 1
|
|
37
39
|
_scan_finished_at_msec = Time.get_ticks_msec()
|
|
38
40
|
|
|
39
41
|
|
|
@@ -658,8 +660,8 @@ func rescan_filesystem(args: Dictionary) -> Dictionary:
|
|
|
658
660
|
# conditions from its progress dialog. Declined and said, so the caller waits and asks again.
|
|
659
661
|
var busy: bool = filesystem.is_scanning() or filesystem.is_importing()
|
|
660
662
|
var started: bool = false
|
|
663
|
+
var completed_before: int = _scans_completed
|
|
661
664
|
if not Read.as_bool(args.get("statusOnly", false)) and not busy:
|
|
662
|
-
var finished_before: int = _scans_finished
|
|
663
665
|
filesystem.scan()
|
|
664
666
|
# Whether the scan ran, read off the editor rather than assumed. `scan()` returns without
|
|
665
667
|
# a word while the thread of the scan before is still to be joined, which is a frame or
|
|
@@ -668,16 +670,19 @@ func rescan_filesystem(args: Dictionary) -> Dictionary:
|
|
|
668
670
|
# downstream, a rescan after 698 files were rewritten answered finished in 1.5s and the
|
|
669
671
|
# one changed file stayed on its old import. A threaded scan reports itself from inside
|
|
670
672
|
# the call, and one run on this thread has finished and signalled before it returns.
|
|
671
|
-
started = filesystem.is_scanning() or
|
|
673
|
+
started = filesystem.is_scanning() or _scans_completed != completed_before
|
|
672
674
|
|
|
673
675
|
# Importing is reported separately from scanning, and a class is not registered until
|
|
674
|
-
# both are done, so a caller watching only one of them can look too early.
|
|
676
|
+
# both are done, so a caller watching only one of them can look too early. The two counts are
|
|
677
|
+
# what a caller waits on: the scan it asked for is over once the count has moved past the one
|
|
678
|
+
# from before it was asked.
|
|
675
679
|
return {
|
|
676
680
|
"ok": true,
|
|
677
681
|
"started": started,
|
|
678
682
|
"scanning": filesystem.is_scanning(),
|
|
679
683
|
"importing": filesystem.is_importing(),
|
|
680
|
-
"
|
|
684
|
+
"scansCompletedBefore": completed_before,
|
|
685
|
+
"scansCompleted": _scans_completed,
|
|
681
686
|
}
|
|
682
687
|
|
|
683
688
|
|
|
@@ -8,6 +8,11 @@ const IMPORTABLE_EXTENSIONS: Array[String] = [
|
|
|
8
8
|
"png", "jpg", "jpeg", "webp", "svg", "wav", "mp3", "ogg", "ttf", "otf", "glb", "gltf", "fbx", "obj"
|
|
9
9
|
]
|
|
10
10
|
|
|
11
|
+
## How many resources a scene's dependencies are followed through before the walk stops: a scene's
|
|
12
|
+
## materials and what they use are a handful, and a project that references its way around a cycle
|
|
13
|
+
## is not walked for ever.
|
|
14
|
+
const DEPENDENCY_WALK_LIMIT: int = 200
|
|
15
|
+
|
|
11
16
|
var _log: Log
|
|
12
17
|
var _files: FileWalk = FileWalk.new()
|
|
13
18
|
|
|
@@ -383,10 +388,6 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
|
|
|
383
388
|
var status: Dictionary = {
|
|
384
389
|
"path": resource_path, "status": "up_to_date", "import_file_exists": true, "source_exists": true
|
|
385
390
|
}
|
|
386
|
-
if FileAccess.get_modified_time(resource_path) > FileAccess.get_modified_time(import_file_path):
|
|
387
|
-
status["status"] = "needs_reimport"
|
|
388
|
-
status["reason"] = "the source changed after it was imported"
|
|
389
|
-
return status
|
|
390
391
|
|
|
391
392
|
# The sidecar alone does not say the import is there: deleting the outputs under
|
|
392
393
|
# .godot/imported is the ordinary way to force one, and a resource whose output is gone was
|
|
@@ -402,23 +403,90 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
|
|
|
402
403
|
status["missing_outputs"] = missing
|
|
403
404
|
return status
|
|
404
405
|
|
|
405
|
-
#
|
|
406
|
-
#
|
|
407
|
-
#
|
|
408
|
-
|
|
409
|
-
var
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if not before.is_empty():
|
|
406
|
+
# By content, as the editor judges it: the hash of the source the import recorded, against the
|
|
407
|
+
# file. By time, an installer that rewrote 31 images byte for byte made every one read as changed,
|
|
408
|
+
# and the editor, which compares the hash, rightly imported none of them.
|
|
409
|
+
var recorded: String = _recorded_source_md5(resource_path)
|
|
410
|
+
var changed: bool = (
|
|
411
|
+
FileAccess.get_md5(resource_path) != recorded
|
|
412
|
+
if not recorded.is_empty()
|
|
413
|
+
else FileAccess.get_modified_time(resource_path) > FileAccess.get_modified_time(import_file_path)
|
|
414
|
+
)
|
|
415
|
+
if changed:
|
|
416
416
|
status["status"] = "needs_reimport"
|
|
417
|
-
status["reason"] = "
|
|
418
|
-
status
|
|
417
|
+
status["reason"] = "the source changed after it was imported"
|
|
418
|
+
return status
|
|
419
|
+
|
|
420
|
+
# A scene built without an image it names: it loads each one as it imports, so one imported
|
|
421
|
+
# before the image was is built without it. Read off what the imported scene depends on, not off
|
|
422
|
+
# file times: a texture a 3D scene uses is reimported compressed after the scene, so its outputs
|
|
423
|
+
# are always the newer, and 33 correct scenes read as stale when judged that way.
|
|
424
|
+
#
|
|
425
|
+
# Followed through the resources the scene depends on, since a post-import script that swaps each
|
|
426
|
+
# material for a saved one leaves the scene depending on the material and only the material on
|
|
427
|
+
# the image: 211 correct scenes read as stale when only the scene's own list was read. And not
|
|
428
|
+
# held against a scene an import script shaped at all, since the script may replace a material
|
|
429
|
+
# with one using other images entirely, which is the scene it was meant to build.
|
|
430
|
+
var without: Array[String] = []
|
|
431
|
+
var images: Array[String] = _images_of(resource_path)
|
|
432
|
+
if not images.is_empty() and not _shaped_by_a_script(import_file_path):
|
|
433
|
+
var depended: Dictionary[String, bool] = _depended_on(resource_path)
|
|
434
|
+
for image: String in images:
|
|
435
|
+
if FileAccess.file_exists(image + ".import") and not depended.has(image):
|
|
436
|
+
without.append(image)
|
|
437
|
+
if not without.is_empty():
|
|
438
|
+
status["status"] = "needs_reimport"
|
|
439
|
+
status["reason"] = "it was imported without images it uses, which were not imported yet"
|
|
440
|
+
status["imported_without"] = without
|
|
419
441
|
return status
|
|
420
442
|
|
|
421
443
|
|
|
444
|
+
## The hash of its source that [param resource_path]'s last import recorded, or "" when there is no
|
|
445
|
+
## record: the editor keeps it beside the outputs, under the same base name with [code].md5[/code].
|
|
446
|
+
static func _recorded_source_md5(resource_path: String) -> String:
|
|
447
|
+
var record: String = (
|
|
448
|
+
"res://.godot/imported/%s-%s.md5" % [resource_path.get_file(), resource_path.md5_text()]
|
|
449
|
+
)
|
|
450
|
+
var config: ConfigFile = ConfigFile.new()
|
|
451
|
+
if config.load(record) != OK:
|
|
452
|
+
return ""
|
|
453
|
+
return str(config.get_value("", "source_md5", ""))
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
## The path of one entry [method ResourceLoader.get_dependencies] lists, past any uid and type.
|
|
457
|
+
static func _path_of(entry: String) -> String:
|
|
458
|
+
var at: int = entry.rfind("::")
|
|
459
|
+
return entry if at < 0 else entry.substr(at + 2)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
## Every path [param resource_path] depends on, through the resources it depends on in turn, as far
|
|
463
|
+
## as [constant DEPENDENCY_WALK_LIMIT] resources; a set, keyed by path.
|
|
464
|
+
static func _depended_on(resource_path: String) -> Dictionary[String, bool]:
|
|
465
|
+
var found: Dictionary[String, bool] = {}
|
|
466
|
+
var pending: Array[String] = [resource_path]
|
|
467
|
+
var walked: int = 0
|
|
468
|
+
while not pending.is_empty() and walked < DEPENDENCY_WALK_LIMIT:
|
|
469
|
+
var next: String = pending.pop_back()
|
|
470
|
+
walked += 1
|
|
471
|
+
for entry: String in ResourceLoader.get_dependencies(next):
|
|
472
|
+
var path: String = _path_of(entry)
|
|
473
|
+
if found.has(path):
|
|
474
|
+
continue
|
|
475
|
+
found[path] = true
|
|
476
|
+
# Only what can hold further references: an image or a sound depends on nothing.
|
|
477
|
+
if path.get_extension().to_lower() in ["tres", "res", "tscn", "scn", "material"]:
|
|
478
|
+
pending.append(path)
|
|
479
|
+
return found
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
## Whether the import of the file [param import_file_path] describes runs a post-import script.
|
|
483
|
+
static func _shaped_by_a_script(import_file_path: String) -> bool:
|
|
484
|
+
var config: ConfigFile = ConfigFile.new()
|
|
485
|
+
if config.load(import_file_path) != OK:
|
|
486
|
+
return false
|
|
487
|
+
return not str(config.get_value("params", "import_script/path", "")).is_empty()
|
|
488
|
+
|
|
489
|
+
|
|
422
490
|
## The files an import wrote, from its sidecar's [code]dest_files[/code]; empty when it lists none.
|
|
423
491
|
static func _outputs_of(import_file_path: String) -> Array[String]:
|
|
424
492
|
var config: ConfigFile = ConfigFile.new()
|
|
@@ -432,25 +500,6 @@ static func _outputs_of(import_file_path: String) -> Array[String]:
|
|
|
432
500
|
return outputs
|
|
433
501
|
|
|
434
502
|
|
|
435
|
-
## When the earliest of [param outputs] was written, or [param fallback] when none is on disk.
|
|
436
|
-
static func _oldest(outputs: Array[String], fallback: String) -> int:
|
|
437
|
-
var oldest: int = -1
|
|
438
|
-
for output: String in outputs:
|
|
439
|
-
if FileAccess.file_exists(output):
|
|
440
|
-
var at: int = FileAccess.get_modified_time(output)
|
|
441
|
-
oldest = at if oldest < 0 else mini(oldest, at)
|
|
442
|
-
return oldest if oldest >= 0 else FileAccess.get_modified_time(fallback)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
## When the latest of [param outputs] was written, or [param fallback] when none is on disk.
|
|
446
|
-
static func _newest(outputs: Array[String], fallback: String) -> int:
|
|
447
|
-
var newest: int = -1
|
|
448
|
-
for output: String in outputs:
|
|
449
|
-
if FileAccess.file_exists(output):
|
|
450
|
-
newest = maxi(newest, FileAccess.get_modified_time(output))
|
|
451
|
-
return newest if newest >= 0 else FileAccess.get_modified_time(fallback)
|
|
452
|
-
|
|
453
|
-
|
|
454
503
|
## The image files a glTF scene refers to by path, resolved against the scene's own directory.
|
|
455
504
|
## Embedded images and every other scene format answer nothing.
|
|
456
505
|
static func _images_of(scene_path: String) -> Array[String]:
|
package/build/index.js
CHANGED
|
@@ -24952,14 +24952,15 @@ function startTimesOf(pids) {
|
|
|
24952
24952
|
var ASK_TIMEOUT_MS = 15000;
|
|
24953
24953
|
async function processTree() {
|
|
24954
24954
|
try {
|
|
24955
|
-
|
|
24956
|
-
|
|
24957
|
-
|
|
24958
|
-
|
|
24959
|
-
|
|
24955
|
+
const tree = process.platform === "win32" ? parseProcessTable(await askWindows(), true) : parseProcessTable(await askPosix());
|
|
24956
|
+
lastListingFailure = undefined;
|
|
24957
|
+
return tree;
|
|
24958
|
+
} catch (error) {
|
|
24959
|
+
lastListingFailure = error instanceof Error ? error.message : String(error);
|
|
24960
24960
|
return;
|
|
24961
24961
|
}
|
|
24962
24962
|
}
|
|
24963
|
+
var lastListingFailure;
|
|
24963
24964
|
function parseProcessTable(printed, started = false) {
|
|
24964
24965
|
const tree = new Map;
|
|
24965
24966
|
for (const line of printed.split(/\r?\n/)) {
|
|
@@ -25094,7 +25095,7 @@ async function askWindows() {
|
|
|
25094
25095
|
"-NoProfile",
|
|
25095
25096
|
"-NonInteractive",
|
|
25096
25097
|
"-Command",
|
|
25097
|
-
`${POWERSHELL_UTF8}Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
|
|
25098
|
+
`${POWERSHELL_UTF8}Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate,CommandLine | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
|
|
25098
25099
|
], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
25099
25100
|
return stdout;
|
|
25100
25101
|
}
|
|
@@ -30118,7 +30119,7 @@ var TOOL_SPECS = [
|
|
|
30118
30119
|
requires: ["projectPath"],
|
|
30119
30120
|
operations: {
|
|
30120
30121
|
status: {
|
|
30121
|
-
summary: "which resources are outdated or failed, or one resource with resourcePath: a source
|
|
30122
|
+
summary: "which resources are outdated or failed, or one resource with resourcePath, judged by content as the editor judges it: a source whose hash differs from the one its import recorded, an import whose output under .godot/imported is gone (missing_outputs), and a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without), each with its reason",
|
|
30122
30123
|
requires: []
|
|
30123
30124
|
},
|
|
30124
30125
|
options: { summary: "the import options of one resource", requires: ["resourcePath"] },
|
|
@@ -34072,7 +34073,10 @@ class GodotServer {
|
|
|
34072
34073
|
adopted.log.finish();
|
|
34073
34074
|
adopted.exitCode = record.exitCode ?? null;
|
|
34074
34075
|
adopted.exitSignal = record.exitSignal ?? null;
|
|
34075
|
-
|
|
34076
|
+
const exitMayBeComing = !alive(record.pid) && !exited(adopted);
|
|
34077
|
+
if (!exitMayBeComing) {
|
|
34078
|
+
adopted.endedUnwatched = !exited(adopted);
|
|
34079
|
+
}
|
|
34076
34080
|
}
|
|
34077
34081
|
this.activeProcess = adopted;
|
|
34078
34082
|
return adopted;
|
|
@@ -34562,6 +34566,7 @@ class GodotServer {
|
|
|
34562
34566
|
const ghosts = blind.stillHeld ?? [];
|
|
34563
34567
|
let waitedForEditorMs = 0;
|
|
34564
34568
|
let first;
|
|
34569
|
+
let completedBefore;
|
|
34565
34570
|
for (;; ) {
|
|
34566
34571
|
const busySince = Date.now();
|
|
34567
34572
|
let wasBusy = false;
|
|
@@ -34576,8 +34581,9 @@ class GodotServer {
|
|
|
34576
34581
|
if (first.isError) {
|
|
34577
34582
|
return first;
|
|
34578
34583
|
}
|
|
34579
|
-
const
|
|
34580
|
-
|
|
34584
|
+
const firstAnswer = asParams(JSON.parse(first.content[0]?.text ?? "{}"));
|
|
34585
|
+
completedBefore = readNumber(firstAnswer, "scansCompletedBefore");
|
|
34586
|
+
if (firstAnswer["started"] !== false || Date.now() - started >= timeoutMs) {
|
|
34581
34587
|
break;
|
|
34582
34588
|
}
|
|
34583
34589
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
@@ -34588,7 +34594,9 @@ class GodotServer {
|
|
|
34588
34594
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
34589
34595
|
const status = asParams(await this.godotBridge.invokeTool("rescan_filesystem", { ...args, statusOnly: true }));
|
|
34590
34596
|
const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
|
|
34591
|
-
|
|
34597
|
+
const completed = readNumber(status, "scansCompleted");
|
|
34598
|
+
const notYetOver = completedBefore !== undefined && completed !== undefined && completed <= completedBefore;
|
|
34599
|
+
busy = Boolean(status["scanning"]) || Boolean(status["importing"]) || pending || notYetOver;
|
|
34592
34600
|
if (busy) {
|
|
34593
34601
|
idleSince = null;
|
|
34594
34602
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
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",
|