extends Node ## Frame and simulation cost in a real match scene at representative maximum occupation: ## six armies at the supply cap and a round-13 wave on all three lanes (synthetic fixture, ## gold granted to the fixture only). Two camera setups are measured one after the other: ## a lane view (Mid, Blau side, grotto in the background) and the whole-map overview, ## where every lane, tower, base and the grotto are drawn at once. VSync off; the ## figures apply to this machine only. ## Run: godot --path . --resolution 1920x1080 res://tools/luneraria/lune_benchmark.tscn -- --out=res://docs/luneraria/logs/g1/perf.json const Game = preload("res://scripts/luneraria/lune_game.gd") const PHASE_SECONDS: float = 6.0 var out: String = "" ## Start of the current frame's main-thread work (set by a node that processes first). var frame_start: int = 0 class FrameClock: extends Node var owner_bench: Node func _process(_delta: float) -> void: owner_bench.frame_start = Time.get_ticks_usec() func _ready() -> void: for arg: String in OS.get_cmdline_user_args(): if arg.begins_with("--out="): out = arg.substr(6) var clock := FrameClock.new() clock.owner_bench = self clock.process_priority = -1000 add_child(clock) call_deferred("run") func mean(values: Array) -> float: var total: float = 0.0 for value: Variant in values: total += float(value) return total / maxf(1.0, values.size()) func measure(game: Node3D, state: LuneMatch) -> Dictionary: for i: int in 30: await get_tree().process_frame var frames: Array[float] = [] var cpu: Array[float] = [] var gpu: Array[float] = [] var draws: Array[float] = [] var primitives: Array[float] = [] var objects: Array[float] = [] var mobile: Array[int] = [] var last: int = Time.get_ticks_usec() var end: int = Time.get_ticks_msec() + int(PHASE_SECONDS * 1000.0) while Time.get_ticks_msec() < end and state.phase == LuneMatch.Phase.COMBAT: await RenderingServer.frame_post_draw var now: int = Time.get_ticks_usec() frames.append((now - last) / 1000.0) last = now # Main-thread time from the first _process of the frame until the frame was drawn. cpu.append((now - frame_start) / 1000.0) gpu.append(RenderingServer.viewport_get_measured_render_time_gpu(get_viewport().get_viewport_rid())) draws.append(Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)) primitives.append(Performance.get_monitor(Performance.RENDER_TOTAL_PRIMITIVES_IN_FRAME)) objects.append(Performance.get_monitor(Performance.RENDER_TOTAL_OBJECTS_IN_FRAME)) mobile.append(state.combat.mobile_count()) frames.sort() return {"frames": frames.size(), "frame_ms_mean": mean(frames), "frame_ms_p95": frames[int(frames.size() * .95)] if not frames.is_empty() else -1.0, "cpu_frame_ms_mean": mean(cpu), "gpu_ms_mean": mean(gpu), "draw_calls_mean": mean(draws), "primitives_mean": mean(primitives), "objects_mean": mean(objects), "mobile_units_mean": mean(mobile), "camera_zoom": game.camera.zoom} func run() -> void: Soundscape.enabled = false DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) var t0: int = Time.get_ticks_usec() var game: Node3D = Game.new() add_child(game) game.start({"seed": 5, "human_slot": 0}) var build_ms: float = (Time.get_ticks_usec() - t0) / 1000.0 var state: LuneMatch = game.match_state state.round_number = 13 for slot: int in range(6): state.players[slot].draft.gold = 100000 state.players[slot].draft.roster.clear() LuneBot.plan(state, slot, "ausgewogen") game._on_ready() RenderingServer.viewport_set_measure_render_time(get_viewport().get_viewport_rid(), true) game.speed_index = 1 game.camera.look_at_point(Vector3(-10, 0, 10), 34.0, true) var lane_view: Dictionary = await measure(game, state) game.camera.overview(true) var overview: Dictionary = await measure(game, state) # Pure simulation cost of one fixed step at this occupation, measured separately. var probe := LuneMatch.new() probe.setup({"seed": 5, "human_slot": -1}) probe.round_number = 13 for slot: int in range(6): probe.players[slot].draft.gold = 100000 LuneBot.plan(probe, slot, "ausgewogen") probe.begin_combat() var start_units: int = probe.combat.mobile_count() var s1: int = Time.get_ticks_usec() for i: int in 200: probe.step_combat() var step_ms: float = (Time.get_ticks_usec() - s1) / 1000.0 / 200.0 var report := {"window": get_window().size, "renderer": RenderingServer.get_current_rendering_method(), "gpu": RenderingServer.get_video_adapter_name(), "cpu": OS.get_processor_name(), "engine": Engine.get_version_info().string, "scene_build_ms": build_ms, "mobile_units_at_start": start_units, "buildings": 18, "sim_step_ms": step_ms, "sim_speed": "2x (40 steps/s)", "lane_view": lane_view, "overview": overview, "video_mem_mb": Performance.get_monitor(Performance.RENDER_VIDEO_MEM_USED) / 1048576.0, "texture_mem_mb": Performance.get_monitor(Performance.RENDER_TEXTURE_MEM_USED) / 1048576.0, "static_mem_mb": Performance.get_monitor(Performance.MEMORY_STATIC) / 1048576.0} var path: String = out if not out.is_empty() else "res://docs/luneraria/logs/perf-%dx%d.json" % [get_window().size.x, get_window().size.y] DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(path.get_base_dir())) var file := FileAccess.open(path, FileAccess.WRITE) file.store_string(JSON.stringify(report, "\t")) file.close() print("LUNE_BENCH ", JSON.stringify(report)) get_tree().quit()