#!/usr/bin/env python3 """Validate Agrarian Unreal stat and Insights profiling markers.""" from pathlib import Path import sys REPO_ROOT = Path(__file__).resolve().parents[1] STATS = [ "STAT_AgrarianGameStateTick", "STAT_AgrarianSurvivalTick", "STAT_AgrarianSkyLightingRefresh", "STAT_AgrarianWeatherAudioRefresh", "STAT_AgrarianFoliageInstanceMutation", "STAT_AgrarianWeatherProviderRequest", "STAT_AgrarianWeatherProviderParse", ] SOURCE_MARKERS = { "Source/AgrarianGame/AgrarianGameState.cpp": [ "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianGameStateTick)", "SCOPE_CYCLE_COUNTER(STAT_AgrarianGameStateTick)", ], "Source/AgrarianGame/AgrarianSurvivalComponent.cpp": [ "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianSurvivalTick)", "SCOPE_CYCLE_COUNTER(STAT_AgrarianSurvivalTick)", ], "Source/AgrarianGame/AgrarianSkyLightingController.cpp": [ "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianSkyLightingRefresh)", "SCOPE_CYCLE_COUNTER(STAT_AgrarianSkyLightingRefresh)", ], "Source/AgrarianGame/AgrarianWeatherAudioController.cpp": [ "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianWeatherAudioRefresh)", "SCOPE_CYCLE_COUNTER(STAT_AgrarianWeatherAudioRefresh)", ], "Source/AgrarianGame/AgrarianFoliagePatch.cpp": [ "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianFoliageClear)", "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianFoliageAddTreeInstance)", "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianFoliageAddShrubInstance)", "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianFoliageAddGrassInstance)", "SCOPE_CYCLE_COUNTER(STAT_AgrarianFoliageInstanceMutation)", ], "Source/AgrarianGame/AgrarianWeatherProviderSubsystem.cpp": [ "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianWeatherProviderRequest)", "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianWeatherProviderFallback)", "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianWeatherProviderParseOpenMeteo)", "TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianWeatherProviderParseNoaaNws)", "SCOPE_CYCLE_COUNTER(STAT_AgrarianWeatherProviderRequest)", "SCOPE_CYCLE_COUNTER(STAT_AgrarianWeatherProviderParse)", ], } def read_text(relative_path: str) -> str: path = REPO_ROOT / relative_path if not path.exists(): raise AssertionError(f"Missing required file: {relative_path}") return path.read_text(encoding="utf-8") def require(needle: str, haystack: str, context: str) -> None: if needle not in haystack: raise AssertionError(f"Missing {needle!r} in {context}") def main() -> int: errors: list[str] = [] try: header = read_text("Source/AgrarianGame/AgrarianPerformanceStats.h") source = read_text("Source/AgrarianGame/AgrarianPerformanceStats.cpp") require("DECLARE_STATS_GROUP(TEXT(\"Agrarian\"), STATGROUP_Agrarian", header, "AgrarianPerformanceStats.h") for stat in STATS: require(f"DECLARE_CYCLE_STAT_EXTERN(", header, "AgrarianPerformanceStats.h") require(stat, header, "AgrarianPerformanceStats.h") require(f"DEFINE_STAT({stat});", source, "AgrarianPerformanceStats.cpp") except AssertionError as exc: errors.append(str(exc)) for relative_path, markers in SOURCE_MARKERS.items(): try: contents = read_text(relative_path) require("#include \"AgrarianPerformanceStats.h\"", contents, relative_path) require("#include \"ProfilingDebugging/CpuProfilerTrace.h\"", contents, relative_path) for marker in markers: require(marker, contents, relative_path) except AssertionError as exc: errors.append(str(exc)) try: roadmap = read_text("AGRARIAN_DEVELOPMENT_ROADMAP.md") require("[x] Add performance profiling markers.", roadmap, "AGRARIAN_DEVELOPMENT_ROADMAP.md") docs = read_text("Docs/TechnicalDesignDocument.md") require("stat Agrarian", docs, "Docs/TechnicalDesignDocument.md") require("Unreal Insights", docs, "Docs/TechnicalDesignDocument.md") except AssertionError as exc: errors.append(str(exc)) if errors: for error in errors: print(f"ERROR: {error}", file=sys.stderr) return 1 print("Performance profiling markers are wired.") return 0 if __name__ == "__main__": raise SystemExit(main())