class_name SimulationPerformanceBudget extends RefCounted ## Lightweight runtime profiler with explicit integer-microsecond samples. ## It is diagnostic state only and deliberately stays outside Save snapshots. signal budget_exceeded(metric_id: StringName, report: Dictionary) enum Aggregation { AVERAGE, P95, MAXIMUM, } const DEFAULT_SAMPLE_CAPACITY := 512 const DEFAULT_BUDGETS := { &"simulation_x1": {&"aggregation": Aggregation.AVERAGE, &"limit_us": 4_000}, &"simulation_x4": {&"aggregation": Aggregation.P95, &"limit_us": 12_000}, &"path_query": {&"aggregation": Aggregation.P95, &"limit_us": 2_000}, &"main_thread_stall": {&"aggregation": Aggregation.MAXIMUM, &"limit_us": 50_000}, &"save": {&"aggregation": Aggregation.MAXIMUM, &"limit_us": 5_000_000}, &"load": {&"aggregation": Aggregation.MAXIMUM, &"limit_us": 8_000_000}, } var sample_capacity := DEFAULT_SAMPLE_CAPACITY var _budgets: Dictionary = {} var _samples: Dictionary = {} var _totals: Dictionary = {} var _sample_counts: Dictionary = {} func _init(capacity := DEFAULT_SAMPLE_CAPACITY) -> void: sample_capacity = maxi(int(capacity), 1) for raw_metric_id: Variant in DEFAULT_BUDGETS: _budgets[StringName(raw_metric_id)] = ( DEFAULT_BUDGETS[raw_metric_id] as Dictionary ).duplicate(true) func set_budget( metric_id: StringName, limit_us: int, aggregation: Aggregation = Aggregation.P95 ) -> bool: if metric_id.is_empty() or limit_us <= 0 \ or aggregation < Aggregation.AVERAGE or aggregation > Aggregation.MAXIMUM: return false _budgets[metric_id] = { &"aggregation": aggregation, &"limit_us": limit_us, } return true func record_sample(metric_id: StringName, duration_us: int) -> Dictionary: if metric_id.is_empty() or duration_us < 0: return {&"ok": false, &"error": "Metric ID and non-negative duration are required."} if not _samples.has(metric_id): _samples[metric_id] = [] var values: Array = _samples[metric_id] values.append(duration_us) if values.size() > sample_capacity: values.pop_front() _totals[metric_id] = int(_totals.get(metric_id, 0)) + duration_us _sample_counts[metric_id] = int(_sample_counts.get(metric_id, 0)) + 1 var report := get_report(metric_id) if bool(report.get(&"budget_exceeded", false)): budget_exceeded.emit(metric_id, report.duplicate(true)) return {&"ok": true, &"report": report} func record_simulation_tick(duration_us: int, speed: int) -> Dictionary: var metric_id := &"simulation_x4" if speed == 4 else &"simulation_x1" return record_sample(metric_id, duration_us) ## Records the full main-thread cost of one rendered/process frame, including ## all game-minute ticks consumed at accelerated speed. This keeps x4 from ## accidentally reporting four individually-fast ticks while the frame stalls. func record_simulation_frame(duration_us: int, speed: int, ticks_run: int) -> Dictionary: if ticks_run <= 0: return {&"ok": false, &"error": "At least one completed tick is required."} var simulation_result := record_simulation_tick(duration_us, speed) var stall_result := record_sample(&"main_thread_stall", duration_us) return { &"ok": bool(simulation_result[&"ok"]) and bool(stall_result[&"ok"]), &"ticks_run": ticks_run, &"simulation": simulation_result, &"main_thread": stall_result, } func get_report(metric_id: StringName) -> Dictionary: var values: Array = _samples.get(metric_id, []) var count := values.size() var lifetime_count := int(_sample_counts.get(metric_id, 0)) var lifetime_total := int(_totals.get(metric_id, 0)) var average_us := lifetime_total / lifetime_count if lifetime_count > 0 else 0 var maximum_us := 0 for raw_value: Variant in values: maximum_us = maxi(maximum_us, int(raw_value)) var p95_us := _percentile(values, 95) var budget: Dictionary = _budgets.get(metric_id, {}) var aggregation := int(budget.get(&"aggregation", Aggregation.P95)) var measured_us := _aggregated_value(aggregation, average_us, p95_us, maximum_us) var limit_us := int(budget.get(&"limit_us", 0)) return { &"metric_id": metric_id, &"window_sample_count": count, &"lifetime_sample_count": lifetime_count, &"average_us": average_us, &"p95_us": p95_us, &"maximum_us": maximum_us, &"aggregation": aggregation, &"measured_us": measured_us, &"limit_us": limit_us, &"budget_exceeded": limit_us > 0 and measured_us > limit_us, } func get_all_reports() -> Array[Dictionary]: var metric_ids: Array[StringName] = [] for raw_metric_id: Variant in _budgets: metric_ids.append(StringName(raw_metric_id)) for raw_metric_id: Variant in _samples: var metric_id := StringName(raw_metric_id) if not metric_ids.has(metric_id): metric_ids.append(metric_id) metric_ids.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b) ) var reports: Array[Dictionary] = [] for metric_id: StringName in metric_ids: reports.append(get_report(metric_id)) return reports func clear(metric_id: StringName = &"") -> void: if metric_id.is_empty(): _samples.clear() _totals.clear() _sample_counts.clear() return _samples.erase(metric_id) _totals.erase(metric_id) _sample_counts.erase(metric_id) func _aggregated_value( aggregation: int, average_us: int, p95_us: int, maximum_us: int ) -> int: match aggregation: Aggregation.AVERAGE: return average_us Aggregation.MAXIMUM: return maximum_us _: return p95_us func _percentile(raw_values: Array, percentile: int) -> int: if raw_values.is_empty(): return 0 var values: Array[int] = [] for raw_value: Variant in raw_values: values.append(int(raw_value)) values.sort() var rank_numerator := clampi(percentile, 0, 100) * values.size() var index := maxi((rank_numerator + 99) / 100 - 1, 0) return values[mini(index, values.size() - 1)]