111 lines
5.0 KiB
GDScript
111 lines
5.0 KiB
GDScript
|
|
extends Control
|
||
|
|
|
||
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
||
|
|
const RESULT_PATH := "user://android_benchmark_result.txt"
|
||
|
|
const FRAME_COUNT := 120
|
||
|
|
|
||
|
|
var status_label: Label
|
||
|
|
|
||
|
|
func _ready() -> void:
|
||
|
|
status_label = Label.new()
|
||
|
|
status_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
|
|
status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
|
|
status_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
|
|
status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
|
|
status_label.add_theme_font_size_override("font_size", 32)
|
||
|
|
add_child(status_label)
|
||
|
|
call_deferred("_run")
|
||
|
|
|
||
|
|
func _set_status(message: String) -> void:
|
||
|
|
status_label.text = message
|
||
|
|
var file := FileAccess.open(RESULT_PATH, FileAccess.WRITE)
|
||
|
|
if file != null:
|
||
|
|
file.store_line(message)
|
||
|
|
file.flush()
|
||
|
|
|
||
|
|
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 index in value.size():
|
||
|
|
value[index] = _restore(value[index])
|
||
|
|
return value
|
||
|
|
|
||
|
|
func _load_payload_threaded() -> Dictionary:
|
||
|
|
var file := FileAccess.open("res://tests/artifacts/all_objects_speed6.adventure.json", FileAccess.READ)
|
||
|
|
if file == null:
|
||
|
|
return {"error": "Cannot open benchmark save: %s" % FileAccess.get_open_error()}
|
||
|
|
var parser := JSON.new()
|
||
|
|
var error := parser.parse(file.get_as_text())
|
||
|
|
if error != OK:
|
||
|
|
return {"error": "JSON parse error at line %d: %s" % [parser.get_error_line(), parser.get_error_message()]}
|
||
|
|
var data = E.decode(_restore(parser.data))
|
||
|
|
if not data is Dictionary or not data.has("state"):
|
||
|
|
return {"error": "Benchmark save has no state"}
|
||
|
|
return {"payload": data}
|
||
|
|
|
||
|
|
func _run() -> void:
|
||
|
|
_set_status("Reading and decoding benchmark save on a worker thread…")
|
||
|
|
var worker := Thread.new()
|
||
|
|
var start_error := worker.start(Callable(self, "_load_payload_threaded"))
|
||
|
|
if start_error != OK:
|
||
|
|
_set_status("ERROR: could not start loader thread (%s)" % start_error)
|
||
|
|
return
|
||
|
|
while worker.is_alive():
|
||
|
|
await get_tree().process_frame
|
||
|
|
var loaded: Dictionary = worker.wait_to_finish()
|
||
|
|
if loaded.has("error"):
|
||
|
|
_set_status("ERROR: " + str(loaded.error))
|
||
|
|
return
|
||
|
|
_set_status("Save decoded. Initializing UI scene…")
|
||
|
|
var payload: Dictionary = loaded.payload
|
||
|
|
var scene: Node = 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"))
|
||
|
|
get_tree().root.add_child(scene)
|
||
|
|
await get_tree().process_frame
|
||
|
|
stage.owniverse.state = payload.state.duplicate(true)
|
||
|
|
stage.owniverse.restore_frame_checkpoint(payload.get("simulation", {}))
|
||
|
|
stage.owniverse.presentation.refresh(stage.owniverse.state, stage.owniverse.catalog)
|
||
|
|
stage.set_owniverse_item_value()
|
||
|
|
stage.stage_page_scroller.scroll_horizontal = 4 * stage.PAGE_WIDTH
|
||
|
|
var initial_requested = stage.owniverse.frame_clock.get("datetime", stage.owniverse.state.time.datetime)
|
||
|
|
var initial_published = stage.owniverse.state.time.datetime
|
||
|
|
stage.owniverse.profile_enabled = true
|
||
|
|
stage.measuring = true
|
||
|
|
_set_status("Running speed 6: 0/%d frames" % FRAME_COUNT)
|
||
|
|
for frame in FRAME_COUNT:
|
||
|
|
await get_tree().process_frame
|
||
|
|
if frame % 5 == 4:
|
||
|
|
_set_status("Running speed 6: %d/%d frames; commits=%d" % [frame + 1, FRAME_COUNT, stage.owniverse.frame_commits])
|
||
|
|
stage.measuring = false
|
||
|
|
var samples: Array = stage.frame_samples
|
||
|
|
if samples.is_empty():
|
||
|
|
_set_status("ERROR: no rendered frame samples; frames=%d" % stage.frame_samples.size())
|
||
|
|
return
|
||
|
|
var total: float = 0.0
|
||
|
|
for sample in samples:
|
||
|
|
total += sample
|
||
|
|
samples.sort()
|
||
|
|
var requested_advance = E.subtract(stage.owniverse.frame_clock.datetime, initial_requested)
|
||
|
|
var published_advance = E.subtract(stage.owniverse.state.time.datetime, initial_published)
|
||
|
|
var process_samples: Array = stage.process_samples
|
||
|
|
process_samples.sort()
|
||
|
|
var process_total: float = 0.0
|
||
|
|
for sample in process_samples:
|
||
|
|
process_total += sample
|
||
|
|
var process_summary := "no _process samples"
|
||
|
|
if not process_samples.is_empty():
|
||
|
|
process_summary = "mean=%.2fms p95=%.2fms max=%.2fms samples=%d" % [process_total / process_samples.size(), process_samples[int(ceil(process_samples.size() * 0.95)) - 1], process_samples.back(), process_samples.size()]
|
||
|
|
var budget: Variant = stage.owniverse.frame_budget
|
||
|
|
var max_slice_summary: String = "none"
|
||
|
|
if budget != null:
|
||
|
|
max_slice_summary = "%.2fms (%s)" % [budget.max_slice_usec / 1000.0, budget.max_slice_phase]
|
||
|
|
var result := "DONE\nFPS=%.2f mean=%.2fms p95=%.2fms max=%.2fms frames=%d commits=%d\nStage _process CPU: %s\nMax uninterrupted simulation slice: %s\nRequested=%s published=%s throughput=%s\nFinal adaptive step=%s\nProfile=%s" % [1000.0 / (total / samples.size()), total / samples.size(), samples[int(ceil(samples.size() * 0.95)) - 1], samples.back(), samples.size(), stage.owniverse.frame_commits, process_summary, max_slice_summary, str(requested_advance), str(published_advance), str(E.divide(published_advance, requested_advance)), str(stage.owniverse.frame_step_seconds), str(stage.owniverse.profile)]
|
||
|
|
_set_status(result)
|
||
|
|
get_tree().quit()
|