Files
owniverse/tests/benchmark_dynamic_cadence_ui.gd
T
Kirill 273093bd9e Разделить частоту обновления часов и расчёта симуляции
Счётчик времени UI обновляется до 10 Гц по запрошенному времени, а тяжёлые циклы по-прежнему запускаются с адаптивным интервалом и обрабатывают весь накопленный промежуток. Обновить тесты и описание модели.
2026-10-17 23:41:18 +03:00

117 lines
5.0 KiB
GDScript

extends SceneTree
## Measures real Stage scheduling on the full-load diagnostic save.
const E = preload("res://world/owniverse/math/exact.gd")
const RUN_SECONDS: float = 10.0
const RESULT_PATH := "res://tests/artifacts/dynamic_cadence_ui_results.json"
func _initialize() -> void:
call_deferred("run")
func restore(value):
if value is float and value == floorf(value) and absf(value) <= 9007199254740991: return int(value)
if value is Dictionary:
for key in value: value[key] = restore(value[key])
elif value is Array:
for i in value.size(): value[i] = restore(value[i])
return value
func prepare(stage, state: Dictionary, adaptive: bool) -> void:
var world = stage.owniverse
world.state = state.duplicate(true)
world.speed = 6
world.adaptive_cadence = adaptive
world.adaptive_cycle_ema_ms = 0.0
world.adaptive_quality = 0
world.adaptive_recovery_cycles = 0
world.cadence_level = 0
world.cadence_recovery_cycles = 0
world.frame_step_seconds = 0.2
world.frame_elapsed_seconds = 0.0
world.frame_clock = world.state.time.duplicate()
world.frame_active_clock = {}
world._resume_clock = {}
world._queued_productions.clear()
world.production_queue_busy = false
world.frame_busy = false
world.frame_commits = 0
world.profile.clear()
stage.frame_samples.clear()
stage.process_samples.clear()
stage.previous_frame = 0
stage.measuring = false
world.presentation.refresh(world.state, world.catalog)
stage.set_owniverse_item_value()
func run_measurement(stage, initial: Dictionary, adaptive: bool) -> Dictionary:
prepare(stage, initial, adaptive)
var world = stage.owniverse
var start_usec: int = Time.get_ticks_usec()
var requested_start: Variant = world.frame_clock.datetime
var published_start: Variant = world.state.time.datetime
stage.measuring = true
while float(Time.get_ticks_usec() - start_usec) / 1000000.0 < RUN_SECONDS:
await process_frame
stage.measuring = false
var measured_frames: int = stage.frame_samples.size()
# Let the final atomic transaction publish before starting the paired run.
while world.frame_busy or world.production_queue_busy:
await process_frame
var elapsed_usec: int = Time.get_ticks_usec() - start_usec
var requested: Variant = E.subtract(world.frame_clock.datetime, requested_start)
var published: Variant = E.subtract(world.state.time.datetime, published_start)
var samples: Array = stage.frame_samples.duplicate()
var total_frame_ms: float = 0.0
for value in samples: total_frame_ms += value
samples.sort()
var result: Dictionary = {
"mode": "adaptive_5_4_3_2" if adaptive else "fixed_5",
"frames": measured_frames,
"elapsed_seconds": float(elapsed_usec) / 1000000.0,
"commits": world.frame_commits,
"completed_cycles_per_second": float(world.frame_commits) * 1000000.0 / elapsed_usec,
"requested_years": E.fixed(requested),
"published_years": E.fixed(published),
"published_to_requested": snappedf(E.approx(E.divide(published, requested)), 0.00001),
"mean_render_fps": float(samples.size()) * 1000.0 / total_frame_ms if total_frame_ms > 0.0 else 0.0,
"render_p95_ms": samples[int(ceil(samples.size() * 0.95)) - 1] if not samples.is_empty() else 0.0,
"render_max_ms": samples.back() if not samples.is_empty() else 0.0,
"final_cadence_hz": 1.0 / world.frame_step_seconds,
"final_adaptive_quality": world.adaptive_quality,
"cpu_profile_usec_calls": world.profile.duplicate(true)
}
print("UI_CADENCE_RESULT ", JSON.stringify(result))
return result
func run() -> void:
var raw = JSON.parse_string(FileAccess.get_file_as_string("res://tests/artifacts/all_objects_speed6.adventure.json"))
assert(raw is Dictionary and raw.get("format_version") == 6)
var payload: Dictionary = E.decode(restore(raw))
var validation: String = root.get_node("SaveManager").validate_data(payload)
assert(validation.is_empty(), validation)
var scene = load("res://world/world.tscn").instantiate()
var stage = scene.get_node("Stage")
stage.owniverse.free()
stage.set_script(load("res://tests/fixtures/benchmark_ui_stage.gd"))
root.add_child(scene)
await process_frame
stage.owniverse.profile_enabled = true
var results: Array[Dictionary] = []
for repeat_index in 2:
if repeat_index % 2 == 0:
results.append(await run_measurement(stage, payload.state, false))
results.append(await run_measurement(stage, payload.state, true))
else:
results.append(await run_measurement(stage, payload.state, true))
results.append(await run_measurement(stage, payload.state, false))
var report: Dictionary = {"machine": OS.get_model_name(), "godot": Engine.get_version_info(), "speed": 6, "wall_seconds_per_run": RUN_SECONDS, "save": "all_objects_speed6.adventure.json", "results": results}
var output := FileAccess.open(RESULT_PATH, FileAccess.WRITE)
assert(output != null)
output.store_string(JSON.stringify(report, "\t", true, true))
output.close()
print("UI_CADENCE_RESULTS ", JSON.stringify(report))
print("Wrote paired UI cadence results: ", RESULT_PATH)
for entity in stage.owniverse.catalog.entities.values(): entity.free()
stage.owniverse.free()
scene.free()
quit()