117 lines
5.0 KiB
GDScript
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()
|