Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
This commit is contained in:
@@ -26,6 +26,9 @@ const DEFAULT_BUDGETS := {
|
||||
var sample_capacity := DEFAULT_SAMPLE_CAPACITY
|
||||
var _budgets: Dictionary = {}
|
||||
var _samples: Dictionary = {}
|
||||
# Derived diagnostic index; never part of game Save snapshots.
|
||||
var _ordered_samples: Dictionary = {}
|
||||
var _ordered_sample_bytes: Dictionary = {}
|
||||
var _totals: Dictionary = {}
|
||||
var _sample_counts: Dictionary = {}
|
||||
|
||||
@@ -59,12 +62,22 @@ func record_sample(metric_id: StringName, duration_us: int) -> Dictionary:
|
||||
if not _samples.has(metric_id):
|
||||
_samples[metric_id] = []
|
||||
var values: Array = _samples[metric_id]
|
||||
var use_index: bool = get_script().resource_path == "res://scripts/core/performance_budget.gd" and _integer_window(values)
|
||||
var ordered: Array[int] = []
|
||||
if use_index: ordered = _validated_sample_order(metric_id, values)
|
||||
values.append(duration_us)
|
||||
if use_index: ordered.insert(ordered.bsearch(duration_us), duration_us)
|
||||
if values.size() > sample_capacity:
|
||||
values.pop_front()
|
||||
var removed: Variant = values.pop_front()
|
||||
if use_index: ordered.remove_at(ordered.bsearch(int(removed)))
|
||||
if use_index:
|
||||
_ordered_sample_bytes[metric_id] = var_to_bytes(values)
|
||||
else:
|
||||
_ordered_samples.erase(metric_id)
|
||||
_ordered_sample_bytes.erase(metric_id)
|
||||
_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)
|
||||
var report: Dictionary = _indexed_report(metric_id, values, ordered) if use_index else get_report(metric_id)
|
||||
if bool(report.get(&"budget_exceeded", false)):
|
||||
budget_exceeded.emit(metric_id, report.duplicate(true))
|
||||
return {&"ok": true, &"report": report}
|
||||
@@ -92,15 +105,25 @@ func record_simulation_frame(duration_us: int, speed: int, ticks_run: int) -> Di
|
||||
|
||||
|
||||
func get_report(metric_id: StringName) -> Dictionary:
|
||||
var values: Array = _samples.get(metric_id, [])
|
||||
var raw: Array = _samples.get(metric_id, [])
|
||||
if get_script().resource_path != "res://scripts/core/performance_budget.gd" or not _integer_window(raw):
|
||||
return SimulationPerformanceBudget._legacy_report(self, metric_id)
|
||||
var ordered: Array[int] = []
|
||||
return _indexed_report(metric_id, raw, ordered, true)
|
||||
|
||||
|
||||
func _indexed_report(metric_id: StringName, values: Array, ordered: Array[int], validate_order: bool = false) -> Dictionary:
|
||||
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)
|
||||
if validate_order: ordered = _validated_sample_order(metric_id, values)
|
||||
var maximum_us := maxi(0, ordered[-1]) if not ordered.is_empty() else 0
|
||||
var p95_us := 0
|
||||
if not ordered.is_empty():
|
||||
var rank_numerator := 95 * ordered.size()
|
||||
var index := maxi((rank_numerator + 99) / 100 - 1, 0)
|
||||
p95_us = ordered[mini(index, ordered.size() - 1)]
|
||||
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)
|
||||
@@ -139,10 +162,14 @@ func get_all_reports() -> Array[Dictionary]:
|
||||
func clear(metric_id: StringName = &"") -> void:
|
||||
if metric_id.is_empty():
|
||||
_samples.clear()
|
||||
_ordered_samples.clear()
|
||||
_ordered_sample_bytes.clear()
|
||||
_totals.clear()
|
||||
_sample_counts.clear()
|
||||
return
|
||||
_samples.erase(metric_id)
|
||||
_ordered_samples.erase(metric_id)
|
||||
_ordered_sample_bytes.erase(metric_id)
|
||||
_totals.erase(metric_id)
|
||||
_sample_counts.erase(metric_id)
|
||||
|
||||
@@ -172,3 +199,55 @@ func _percentile(raw_values: Array, percentile: int) -> int:
|
||||
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)]
|
||||
|
||||
|
||||
static func _integer_window(values: Array) -> bool:
|
||||
for raw_value: Variant in values:
|
||||
if typeof(raw_value) != TYPE_INT: return false
|
||||
return true
|
||||
|
||||
|
||||
func _validated_sample_order(metric_id: StringName, values: Array) -> Array[int]:
|
||||
# No retained state for unknown metrics; raw dictionary removal also drops its index.
|
||||
if not _samples.has(metric_id):
|
||||
_ordered_samples.erase(metric_id)
|
||||
_ordered_sample_bytes.erase(metric_id)
|
||||
return []
|
||||
# Complete immutable raw bytes detect in-place/replaced windows, not just length.
|
||||
var current_bytes := var_to_bytes(values)
|
||||
if not _ordered_samples.has(metric_id) or _ordered_sample_bytes.get(metric_id, PackedByteArray()) != current_bytes:
|
||||
var ordered: Array[int] = []
|
||||
for raw_value: Variant in values: ordered.append(int(raw_value))
|
||||
ordered.sort()
|
||||
_ordered_samples[metric_id] = ordered
|
||||
_ordered_sample_bytes[metric_id] = current_bytes
|
||||
return _ordered_samples[metric_id]
|
||||
|
||||
|
||||
static func _legacy_report(owner: SimulationPerformanceBudget, metric_id: StringName) -> Dictionary:
|
||||
var values: Array = owner._samples.get(metric_id, [])
|
||||
var count := values.size()
|
||||
var lifetime_count := int(owner._sample_counts.get(metric_id, 0))
|
||||
var lifetime_total := int(owner._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 := owner._percentile(values, 95)
|
||||
var budget: Dictionary = owner._budgets.get(metric_id, {})
|
||||
var aggregation := int(budget.get(&"aggregation", Aggregation.P95))
|
||||
var measured_us := owner._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,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user