gdharness 1.2.6 → 1.2.7

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
@@ -42721,7 +42721,7 @@ function callersOf(totals, name, scripts) {
42721
42721
  sites.sort((a, b) => (b.withinTotalMs ?? -1) - (a.withinTotalMs ?? -1) || a.script.localeCompare(b.script) || a.line - b.line);
42722
42722
  return { target, sites: sites.slice(0, CALL_SITES), omitted: Math.max(0, sites.length - CALL_SITES) };
42723
42723
  }
42724
- var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", ms = (seconds) => Math.round(seconds * 1e6) / 1000, RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd", PROFILER_PROBE = "_profiler_probe", PROFILER_CALIBRATOR = "_calibrate_the_profiler", CALL_SITES = 40;
42724
+ var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START", ms = (seconds) => Math.round(seconds * 1e6) / 1000, RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd", PROFILER_PROBE = "_profiler_probe", PROFILER_CALIBRATOR = "_calibrate_the_profiler", CALL_SITES = 40;
42725
42725
  var init_script_profile = __esm(() => {
42726
42726
  init_gdscript_source();
42727
42727
  init_godot_variant();
@@ -46929,10 +46929,11 @@ class GodotServer {
46929
46929
  const startedAt = Date.now();
46930
46930
  const transcript = openTranscript(startedAt);
46931
46931
  closeSync2(transcript.fd);
46932
+ const gameEnv = profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env;
46932
46933
  const launched = await launchOutsideTheTree({
46933
46934
  command: godotPath,
46934
46935
  args: cmdArgs,
46935
- ...env === undefined ? {} : { env },
46936
+ ...gameEnv === undefined ? {} : { env: gameEnv },
46936
46937
  run: {
46937
46938
  transcript: transcript.path,
46938
46939
  startedAt,
@@ -22,6 +22,9 @@ const DEBUGGER_CLASS: String = "ScriptEditorDebugger"
22
22
  const PROFILE_CAPTURE: String = "gdharness"
23
23
  const PROFILE_SENT: String = "gdharness:profile_sent"
24
24
  const PROFILE_RECEIVED: String = "gdharness:profile_received"
25
+ ## Set in the environment a played game inherits, when the play is to be profiled from its start.
26
+ ## Kept in step with the runtime addon's runtime_autoload.gd.
27
+ const PROFILE_FROM_START: String = "GDHARNESS_PROFILE_FROM_START"
25
28
 
26
29
 
27
30
  ## The editor's debugger sessions, numbered as the editor numbers its debugger tabs.
@@ -106,6 +109,10 @@ func profile_start(args: Dictionary) -> Dictionary:
106
109
  _frame_functions = asked if asked > 0 else FRAME_FUNCTIONS
107
110
  if Read.as_bool(args.get("arm", false)):
108
111
  _armed = true
112
+ # The game the editor starts next inherits the editor's environment, and this tells its
113
+ # runtime addon to hold its start until the profiler is on, which the session's start
114
+ # switches on with a message the game reads only on a later poll.
115
+ OS.set_environment(PROFILE_FROM_START, "1")
109
116
  return {"ok": true, "started": false, "armed": true, "session": -1}
110
117
  var active: int = _active_session()
111
118
  if active < 0:
@@ -137,6 +144,7 @@ func profile_read(_args: Dictionary) -> Dictionary:
137
144
 
138
145
  func _reset() -> void:
139
146
  _armed = false
147
+ OS.unset_environment(PROFILE_FROM_START)
140
148
  _session = -1
141
149
  _frames = 0
142
150
  _complete = false
@@ -172,6 +180,8 @@ func _on_session_started(session_id: int) -> void:
172
180
  _reset()
173
181
  return
174
182
  _armed = false
183
+ # The game has started with it by now, and a later play must not.
184
+ OS.unset_environment(PROFILE_FROM_START)
175
185
  if _hook() > 0:
176
186
  _switch_on(session_id)
177
187
 
@@ -47,6 +47,13 @@ const PROFILER_PROBE_CALLS: int = 10000
47
47
  ## on every 2048th call and only counts the others.
48
48
  const POLLS_PER_READ: int = 2048
49
49
 
50
+ ## Set in a game's environment by whatever starts it to be profiled from its start: the server for
51
+ ## a run it spawns, the editor for a play. Kept in step with `PROFILE_FROM_START` in the server.
52
+ const PROFILE_FROM_START: String = "GDHARNESS_PROFILE_FROM_START"
53
+
54
+ ## How long such a game waits for its debugger to switch the profiler on before carrying on.
55
+ const PROFILER_WAIT_MS: int = 5000
56
+
50
57
  ## How the engine is told to run a script instead of the game: `godot -s thing.gd`.
51
58
  const SCRIPT_FLAGS: PackedStringArray = ["-s", "--script"]
52
59
 
@@ -134,6 +141,37 @@ func _init() -> void:
134
141
  "wait_signal": _waits.wait_signal,
135
142
  "wait_until": _waits.wait_until,
136
143
  }
144
+ _wait_for_the_profiler()
145
+
146
+
147
+ ## Holds the game's start until the profiler is on, when whatever started the game asked for a
148
+ ## profile from its start. The debugger switches the profiler on with a message the game reads only
149
+ ## on a poll, which comes every [constant POLLS_PER_READ] lines of script, so the main scene's _ready
150
+ ## could run first: a game played on a macOS runner counted 25 calls of a function called 26 times,
151
+ ## the first of them from _ready. Run at the autoload's construction, before the main scene loads.
152
+ func _wait_for_the_profiler() -> void:
153
+ if OS.get_environment(PROFILE_FROM_START).is_empty():
154
+ return
155
+ # Read once: a process this game starts inherits its environment and must not wait as well.
156
+ OS.unset_environment(PROFILE_FROM_START)
157
+ if not EngineDebugger.is_active():
158
+ return
159
+ var deadline: int = Time.get_ticks_msec() + PROFILER_WAIT_MS
160
+ while not EngineDebugger.is_profiling("servers"):
161
+ if Time.get_ticks_msec() >= deadline:
162
+ push_warning(
163
+ (
164
+ (
165
+ "gdharness: this game was started to be profiled from its start, and the profiler"
166
+ + " was not on %d ms later, so what ran before it is not counted"
167
+ )
168
+ % PROFILER_WAIT_MS
169
+ )
170
+ )
171
+ return
172
+ for _poll: int in POLLS_PER_READ:
173
+ EngineDebugger.line_poll()
174
+ OS.delay_msec(1)
137
175
 
138
176
 
139
177
  func _ready() -> void:
package/build/index.js CHANGED
@@ -33393,6 +33393,7 @@ function queuedMessagesOf(projectGodot) {
33393
33393
  return typeof set === "number" && Number.isInteger(set) && set > 0 ? set : QUEUE_DEFAULT;
33394
33394
  }
33395
33395
  var UNNAMED_PREFIX = "#";
33396
+ var PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START";
33396
33397
  function signatureParts(signature) {
33397
33398
  const parts = signature.split("::");
33398
33399
  if (parts.length < 3) {
@@ -39426,10 +39427,11 @@ class GodotServer {
39426
39427
  const startedAt = Date.now();
39427
39428
  const transcript = openTranscript(startedAt);
39428
39429
  closeSync2(transcript.fd);
39430
+ const gameEnv = profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env;
39429
39431
  const launched = await launchOutsideTheTree({
39430
39432
  command: godotPath,
39431
39433
  args: cmdArgs,
39432
- ...env === undefined ? {} : { env },
39434
+ ...gameEnv === undefined ? {} : { env: gameEnv },
39433
39435
  run: {
39434
39436
  transcript: transcript.path,
39435
39437
  startedAt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
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",