gdharness 1.3.57 → 1.3.58
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
|
@@ -1763,6 +1763,9 @@ function nonEmpty(value) {
|
|
|
1763
1763
|
function keptSlotsOf(count) {
|
|
1764
1764
|
return Math.max(2, Math.floor(count / 4));
|
|
1765
1765
|
}
|
|
1766
|
+
function reservedSlotOf(count) {
|
|
1767
|
+
return count - Math.max(1, count - keptSlotsOf(count)) >= 2 ? count - 1 : null;
|
|
1768
|
+
}
|
|
1766
1769
|
function gamesKeptOf(kept) {
|
|
1767
1770
|
return Math.max(0, Math.min(Math.floor(kept / 2), kept - GATE_KEPT - 1));
|
|
1768
1771
|
}
|
|
@@ -1777,7 +1780,8 @@ function slotsDrawnOn(count, taker, queue) {
|
|
|
1777
1780
|
return { numbers: every };
|
|
1778
1781
|
}
|
|
1779
1782
|
if (taker === "tier") {
|
|
1780
|
-
|
|
1783
|
+
const reserve = reservedSlotOf(count);
|
|
1784
|
+
return { numbers: [...kept].reverse().filter((slot) => slot !== reserve), limit: "tier" };
|
|
1781
1785
|
}
|
|
1782
1786
|
return { numbers: kept.slice(0, Math.max(1, kept.length - GATE_KEPT)), limit: "kept" };
|
|
1783
1787
|
}
|
|
@@ -1926,7 +1930,7 @@ class EngineSlots {
|
|
|
1926
1930
|
try {
|
|
1927
1931
|
for (;; ) {
|
|
1928
1932
|
signal?.throwIfAborted();
|
|
1929
|
-
const lock = await this.takeFree(game);
|
|
1933
|
+
const lock = await this.takeFree(game, Date.now() - began);
|
|
1930
1934
|
if (lock !== null) {
|
|
1931
1935
|
return lock;
|
|
1932
1936
|
}
|
|
@@ -1958,7 +1962,7 @@ class EngineSlots {
|
|
|
1958
1962
|
}
|
|
1959
1963
|
}
|
|
1960
1964
|
}
|
|
1961
|
-
async takeFree(game = false) {
|
|
1965
|
+
async takeFree(game = false, waitedMs = 0) {
|
|
1962
1966
|
mkdirSync2(this.directory, { recursive: true });
|
|
1963
1967
|
await this.sweep();
|
|
1964
1968
|
const inherited = this.inherited === null ? await this.inheritedLock() : null;
|
|
@@ -1967,11 +1971,14 @@ class EngineSlots {
|
|
|
1967
1971
|
return inherited;
|
|
1968
1972
|
}
|
|
1969
1973
|
const taker = this.taker();
|
|
1970
|
-
|
|
1974
|
+
const aged = taker === "other" && !game && waitedMs >= ONE_OFF_AGED_MS;
|
|
1975
|
+
this.drawn = slotsDrawnOn(this.count, taker, taker === "gate" || taker === "timing" || aged ? "none" : await this.gateQueue());
|
|
1971
1976
|
if (game && this.drawn.limit === "kept") {
|
|
1972
|
-
const
|
|
1973
|
-
|
|
1974
|
-
|
|
1977
|
+
const reserve = reservedSlotOf(this.count);
|
|
1978
|
+
this.drawn = { ...this.drawn, numbers: this.drawn.numbers.filter((slot) => slot !== reserve) };
|
|
1979
|
+
const kept = this.count - Math.max(1, this.count - keptSlotsOf(this.count));
|
|
1980
|
+
const games = await this.heldByGames(Array.from({ length: kept }, (_, at) => this.count - kept + at));
|
|
1981
|
+
if (games.length >= gamesKeptOf(kept)) {
|
|
1975
1982
|
this.drawn = { numbers: games, limit: "games" };
|
|
1976
1983
|
return null;
|
|
1977
1984
|
}
|
|
@@ -2122,6 +2129,25 @@ class EngineSlots {
|
|
|
2122
2129
|
await this.cleared(join5(this.directory, name), false);
|
|
2123
2130
|
}
|
|
2124
2131
|
}
|
|
2132
|
+
const marks = join5(this.directory, WAITING_FOLDER);
|
|
2133
|
+
let waits;
|
|
2134
|
+
try {
|
|
2135
|
+
waits = readdirSync(marks);
|
|
2136
|
+
} catch {
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
for (const name of waits) {
|
|
2140
|
+
let content;
|
|
2141
|
+
try {
|
|
2142
|
+
content = readWhole(join5(marks, name));
|
|
2143
|
+
} catch {
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
const pid = content === null ? undefined : holderOf(content)?.pid;
|
|
2147
|
+
if (pid !== undefined && !alive(pid)) {
|
|
2148
|
+
removeWhole(join5(marks, name));
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2125
2151
|
}
|
|
2126
2152
|
takenOver(lock, holder) {
|
|
2127
2153
|
return this.cleared(lock, true, holder);
|
|
@@ -2344,7 +2370,7 @@ async function inASlot(body) {
|
|
|
2344
2370
|
held.release();
|
|
2345
2371
|
}
|
|
2346
2372
|
}
|
|
2347
|
-
var HELD_VARIABLE = "GODOT_ENGINE_HELD", SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", QUEUED_FOLDER = "queued", SHARE_VARIABLE = "GODOT_ENGINE_SHARE", SHARE_COUNT_VARIABLE = "GODOT_ENGINE_SHARE_COUNT", GATE_KEPT = 1, TIMED_VARIABLE = "GODOT_ENGINE_TIMED", TIER_VARIABLE = "GODOT_ENGINE_TIER", SEATED_SUFFIX = ".seated", waitsMarked = 0, ENGINE_PID_LOOK_MS = 100, ours, saidTo, takenBy;
|
|
2373
|
+
var HELD_VARIABLE = "GODOT_ENGINE_HELD", SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", QUEUED_FOLDER = "queued", SHARE_VARIABLE = "GODOT_ENGINE_SHARE", SHARE_COUNT_VARIABLE = "GODOT_ENGINE_SHARE_COUNT", GATE_KEPT = 1, ONE_OFF_AGED_MS = 120000, TIMED_VARIABLE = "GODOT_ENGINE_TIMED", TIER_VARIABLE = "GODOT_ENGINE_TIER", SEATED_SUFFIX = ".seated", waitsMarked = 0, ENGINE_PID_LOOK_MS = 100, ours, saidTo, takenBy;
|
|
2348
2374
|
var init_engine_slots = __esm(() => {
|
|
2349
2375
|
init_alive();
|
|
2350
2376
|
init_cache_home();
|
|
@@ -18158,7 +18184,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18158
18184
|
direction: {
|
|
18159
18185
|
type: "string",
|
|
18160
18186
|
enum: ["forward", "reverse"],
|
|
18161
|
-
description: "forward: what this resource loads or names. Each file named by its path carries kind, as reverse words it: ext_resource, extends, preload, load, include, or path for a path only written in a string, which the file itself may never load. Each reference closing a cycle is marked circular, with the cycle under circular_references. A script's dependencies include every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. A file's dependencies are listed at its first entry, and again further down only where the depth cut that list short and more is left; every other entry for a file naming any carries dependencies_listed_above. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
|
|
18187
|
+
description: "forward: what this resource loads or names. Each file named by its path carries kind, as reverse words it: ext_resource, extends, preload, load, include, or path for a path only written in a string, which the file itself may never load. Each reference closing a cycle is marked circular, with the cycle under circular_references. A script's dependencies include every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. A file's dependencies are listed at its first entry, and again further down only where the depth cut that list short and more is left; every other entry for a file naming any carries dependencies_listed_above. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. A file whose header, or an import sidecar, claims the same uid as its own is no use either: it is listed under shares_uid, since the engine maps a uid to one file and loads that one wherever the uid is named. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
|
|
18162
18188
|
},
|
|
18163
18189
|
depth: {
|
|
18164
18190
|
type: "integer",
|
|
@@ -16,8 +16,9 @@ const SETTLE_MS: int = 8000
|
|
|
16
16
|
## How long a window with nothing in it is walked before the client stops: a game building its tree
|
|
17
17
|
## fills the window within a few frames of being asked, and one building none never does.
|
|
18
18
|
const EMPTY_MS: int = 4000
|
|
19
|
-
## How long starting PowerShell
|
|
20
|
-
## waits on the machine alone: a warm start takes under a second,
|
|
19
|
+
## How long starting PowerShell, loading UI Automation and the client's other cold loads may take
|
|
20
|
+
## before the client is stopped. It waits on the machine alone: a warm start takes under a second,
|
|
21
|
+
## and a cold one on a loaded Windows
|
|
21
22
|
## runner took the whole of a single 23 s budget meant for the walk as well.
|
|
22
23
|
const START_MS: int = 45000
|
|
23
24
|
## How long the walks may take beyond [constant SETTLE_MS] a window once UI Automation is loaded,
|
|
@@ -39,6 +40,7 @@ const NODE_ID: String = "_node_instance"
|
|
|
39
40
|
const WALK: String = r"""
|
|
40
41
|
$ErrorActionPreference = 'Stop'
|
|
41
42
|
function Answer($value) {
|
|
43
|
+
Mark 'answering'
|
|
42
44
|
[System.IO.File]::WriteAllText($Out, (ConvertTo-Json -InputObject $value -Depth 100 -Compress))
|
|
43
45
|
Mark 'answered'
|
|
44
46
|
}
|
|
@@ -48,7 +50,6 @@ function Mark($stage) {
|
|
|
48
50
|
try {
|
|
49
51
|
Mark 'began'
|
|
50
52
|
Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes
|
|
51
|
-
Mark 'loaded'
|
|
52
53
|
$walker = [System.Windows.Automation.TreeWalker]::RawViewWalker
|
|
53
54
|
function Patterned($element, $name) {
|
|
54
55
|
$pattern = ("System.Windows.Automation.$($name)Pattern" -as [type])::Pattern
|
|
@@ -70,6 +71,10 @@ try {
|
|
|
70
71
|
} catch {
|
|
71
72
|
$script:unread = "$_"
|
|
72
73
|
}
|
|
74
|
+
# Serialised once here, since the first ConvertTo-Json loads its serialiser, and on a loaded
|
|
75
|
+
# machine that cold load belongs to the start's budget, not to the walks'.
|
|
76
|
+
$null = ConvertTo-Json -InputObject @{ warm = @(1) } -Depth 100 -Compress
|
|
77
|
+
Mark 'loaded'
|
|
73
78
|
function FullDescription($element) {
|
|
74
79
|
if ($null -ne $script:unread) { return '' }
|
|
75
80
|
try {
|
|
@@ -281,7 +286,9 @@ func get_accessibility(params: Dictionary) -> Dictionary:
|
|
|
281
286
|
var looked_at: int = began_at
|
|
282
287
|
while OS.is_process_running(pid):
|
|
283
288
|
var now: int = Time.get_ticks_msec()
|
|
284
|
-
|
|
289
|
+
# Read again at the deadline too, so an answer marked since the last look is taken rather
|
|
290
|
+
# than stopped.
|
|
291
|
+
if now - looked_at >= LOOK_MS or now > deadline:
|
|
285
292
|
looked_at = now
|
|
286
293
|
var marked: String = FileAccess.get_file_as_string(progress)
|
|
287
294
|
if not walking and marked.contains("loaded "):
|
|
@@ -756,8 +763,8 @@ static func _marks(progress: String) -> Array:
|
|
|
756
763
|
|
|
757
764
|
## How far a client started at [param started_ms] and stopped at [param stopped_ms] got by the marks
|
|
758
765
|
## in [param progress], said for a caller whose read it stopped: the time went to starting
|
|
759
|
-
## PowerShell, loading UI Automation, the walks,
|
|
760
|
-
## machine from a window that never answers.
|
|
766
|
+
## PowerShell, loading UI Automation, the walks, after the last walk, or writing the answer, and
|
|
767
|
+
## which tells a loaded machine from a window that never answers.
|
|
761
768
|
static func _reached(progress: String, started_ms: int, stopped_ms: int) -> String:
|
|
762
769
|
var marks: Array = _marks(progress)
|
|
763
770
|
var at: Dictionary = {}
|
|
@@ -786,7 +793,7 @@ static func _reached(progress: String, started_ms: int, stopped_ms: int) -> Stri
|
|
|
786
793
|
)
|
|
787
794
|
% [began, loaded]
|
|
788
795
|
)
|
|
789
|
-
|
|
796
|
+
var walked: String = (
|
|
790
797
|
(
|
|
791
798
|
"PowerShell began the client after %d ms and loaded UI Automation in %d ms, and the "
|
|
792
799
|
+ "windows were walked %d time%s, the last finding %s element%s and ending %d ms "
|
|
@@ -802,6 +809,10 @@ static func _reached(progress: String, started_ms: int, stopped_ms: int) -> Stri
|
|
|
802
809
|
stopped_ms - last_at,
|
|
803
810
|
]
|
|
804
811
|
)
|
|
812
|
+
if not at.has("answering"):
|
|
813
|
+
return walked
|
|
814
|
+
var answering: int = stopped_ms - at["answering"]
|
|
815
|
+
return walked + ", which had been writing the answer for %d ms" % answering
|
|
805
816
|
|
|
806
817
|
|
|
807
818
|
## Where the time of a client started at [param started_ms] and done at [param done_ms] went, by
|
|
@@ -281,6 +281,8 @@ func find_resource_usages(params: Dictionary) -> Dictionary:
|
|
|
281
281
|
var by_kind: Dictionary = {}
|
|
282
282
|
var total: int = 0
|
|
283
283
|
var in_code: int = 0
|
|
284
|
+
var shares_uid: Array[String] = []
|
|
285
|
+
var target_uid: String = target["uid"]
|
|
284
286
|
|
|
285
287
|
for file_path: String in all_files:
|
|
286
288
|
# Its own sidecar names it as the file it was imported from, which is not a use of it.
|
|
@@ -305,6 +307,11 @@ func find_resource_usages(params: Dictionary) -> Dictionary:
|
|
|
305
307
|
var references: Array[Dictionary] = []
|
|
306
308
|
for i: int in range(lines.size()):
|
|
307
309
|
var line: String = lines[i]
|
|
310
|
+
# A file declaring the target's uid as its own is not using it: a resource copied with
|
|
311
|
+
# its header claims the same uid, and its header line was answered as a use by path.
|
|
312
|
+
if not target_uid.is_empty() and _own_uid(line, i, file_path) == target_uid:
|
|
313
|
+
shares_uid.append(file_path)
|
|
314
|
+
continue
|
|
308
315
|
var by_its_path: bool = _names_target(line, file_path, target)
|
|
309
316
|
var by_its_name: bool = false
|
|
310
317
|
var name_in_comment: bool = false
|
|
@@ -408,9 +415,30 @@ func find_resource_usages(params: Dictionary) -> Dictionary:
|
|
|
408
415
|
}
|
|
409
416
|
if not autoload_named.is_empty():
|
|
410
417
|
answer["autoload"] = autoload_named
|
|
418
|
+
if not shares_uid.is_empty():
|
|
419
|
+
answer["shares_uid"] = shares_uid
|
|
411
420
|
return answer
|
|
412
421
|
|
|
413
422
|
|
|
423
|
+
# The uid [param line], line [param index] of [param file_path], declares as the file's own: a
|
|
424
|
+
# scene's or a resource's header, or an import sidecar's uid of the file it imports; empty for any
|
|
425
|
+
# other line.
|
|
426
|
+
static func _own_uid(line: String, index: int, file_path: String) -> String:
|
|
427
|
+
var trimmed: String = line.strip_edges()
|
|
428
|
+
var declares: bool = (
|
|
429
|
+
(
|
|
430
|
+
index == 0
|
|
431
|
+
and file_path.get_extension() in ["tscn", "tres"]
|
|
432
|
+
and (trimmed.begins_with("[gd_scene") or trimmed.begins_with("[gd_resource"))
|
|
433
|
+
)
|
|
434
|
+
or (file_path.get_extension() == "import" and trimmed.begins_with('uid="'))
|
|
435
|
+
)
|
|
436
|
+
if not declares:
|
|
437
|
+
return ""
|
|
438
|
+
var found: RegExMatch = GdhPatterns.compiled('uid="(uid://[^"]+)"').search(trimmed)
|
|
439
|
+
return "" if found == null else found.get_string(1)
|
|
440
|
+
|
|
441
|
+
|
|
414
442
|
# The class_name a script declares, or empty for a scene, a resource or a script without one.
|
|
415
443
|
func _declared_class_name(path: String) -> String:
|
|
416
444
|
if not path.ends_with(".gd"):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.58",
|
|
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",
|