OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_hitch_log.gd

121 lines · gd ref:
extends Node
## GATE: the hitch logger catches a long frame and puts the time in the right
## phase (hyper-games #93 §2 -- ashatron's 210-613 ms frames).
##
##   "$G" --path . --rendering-driver vulkan res://scenes/test/gate_hitch_log.tscn
##
## Needs a window: the draw phase is timed from RenderingServer's frame_pre_draw /
## frame_post_draw, which a headless run never emits.
##
## After a 3 s baseline it injects, one frame each, a known 150 ms stall:
##   1. in a script's _process            -> a hitch with process_ms >= 140
##   2. in a _physics_process             -> a hitch with physics_ms >= 140
##   3. in a frame_pre_draw handler       -> a hitch with draw_ms    >= 140
##   4. 400 nodes added + 120 ms in _process -> nodes_added >= 400
##   5. in a call_deferred                -> process_ms >= 140 (deferred calls run
##                                           in SceneTree::process, outside _process)
##   6. in a coroutine resumed on process_frame -> process_ms >= 140 (how a map
##                                           change from a script's await lands)
## and checks that the ordinary frames in between were NOT logged as hitches.
## Writes to a scratch log, never the player's user://hitches.log.

const HitchLog := preload("res://scenes/game/hitch_log.gd")
const STALL_MS := 150

var _log: Node = null
var _stall_process := false
var _stall_physics := false
var _stall_draw := false
var _burst := false
var _fails := 0


func _ready() -> void:
	_run.call_deferred()


func _process(_delta: float) -> void:
	if _stall_process:
		_stall_process = false
		OS.delay_msec(STALL_MS)
	if _burst:
		_burst = false
		var holder := Node.new()
		holder.name = "BurstHolder"
		add_child(holder)
		for i in 400:
			holder.add_child(Node.new())
		OS.delay_msec(120)


func _physics_process(_delta: float) -> void:
	if _stall_physics:
		_stall_physics = false
		OS.delay_msec(STALL_MS)


func _stall_deferred() -> void:
	OS.delay_msec(STALL_MS)


func _on_pre_draw() -> void:
	if _stall_draw:
		_stall_draw = false
		OS.delay_msec(STALL_MS)


func _frames(n: int) -> void:
	for i in n:
		await get_tree().process_frame


func _expect(label: String, field: String, minimum: float) -> void:
	await _frames(20)
	var hs: Array = _log.call("get_hitches")
	if hs.is_empty():
		print("[GATE-HITCH] FAIL %s: no hitch logged" % label)
		_fails += 1
		return
	var h: Dictionary = hs[-1]
	var v := float(h.get(field, -1.0))
	var ok := v >= minimum
	print("[GATE-HITCH] %s %s: %s=%.1f (wall %.1f, phys %.1f proc %.1f draw %.1f other %.1f)" % [
			"PASS" if ok else "FAIL", label, field, v, float(h.get("wall_ms", -1)),
			float(h.get("physics_ms", -1)), float(h.get("process_ms", -1)),
			float(h.get("draw_ms", -1)), float(h.get("other_ms", -1))])
	if not ok:
		_fails += 1


func _run() -> void:
	_log = HitchLog.new()
	_log.set("log_path", "user://gate_hitch_log.jsonl")
	get_tree().root.add_child(_log)
	# Connected AFTER the logger's own handler, so the stall lands inside its
	# pre_draw -> post_draw window.
	RenderingServer.frame_pre_draw.connect(_on_pre_draw)
	await _frames(180)
	var baseline := (_log.call("get_hitches") as Array).size()
	print("[GATE-HITCH] baseline hitches: %d" % baseline)

	_stall_process = true
	await _expect("process stall", "process_ms", 140.0)
	_stall_physics = true
	await _expect("physics stall", "physics_ms", 140.0)
	_stall_draw = true
	await _expect("draw stall", "draw_ms", 140.0)
	_burst = true
	await _expect("node burst", "nodes_added", 400.0)
	_stall_deferred.call_deferred()
	await _expect("deferred-call stall", "process_ms", 140.0)
	await get_tree().process_frame
	OS.delay_msec(STALL_MS)
	await _expect("process_frame coroutine stall", "process_ms", 140.0)

	var total := (_log.call("get_hitches") as Array).size()
	if total - baseline != 6:
		print("[GATE-HITCH] FAIL: expected exactly 6 new hitches, got %d" % (total - baseline))
		_fails += 1
	print("RESULT: %s" % ("PASS" if _fails == 0 else "FAIL"))
	get_tree().quit(0 if _fails == 0 else 1)