feat: Add comprehensive profiling system with F3 overlay

Add real-time performance profiling infrastructure to monitor frame times,
render performance, and identify bottlenecks.

Features:
- Profiler.h: ScopedTimer RAII helper for automatic timing measurements
- ProfilerOverlay: F3-togglable overlay displaying real-time metrics
- Detailed timing breakdowns: grid rendering, entity rendering, FOV,
  Python callbacks, and animation updates
- Per-frame counters: cells rendered, entities rendered, draw calls
- Performance color coding: green (<16ms), yellow (<33ms), red (>33ms)
- Benchmark suite: static grid and moving entities performance tests

Integration:
- GameEngine: Integrated profiler overlay with F3 toggle
- UIGrid: Added timing instrumentation for grid and entity rendering
- Metrics tracked in ProfilingMetrics struct with 60-frame averaging

Usage:
- Press F3 in-game to toggle profiler overlay
- Run benchmarks with tests/benchmark_*.py scripts
- ScopedTimer automatically measures code block execution time

This addresses issue #104 (Basic profiling/metrics).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
John McCardle 2025-10-25 00:45:44 -04:00
commit e9e9cd2f81
8 changed files with 715 additions and 29 deletions

View file

@ -42,8 +42,11 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg)
updateViewport();
scene = "uitest";
scenes["uitest"] = new UITestScene(this);
McRFPy_API::game = this;
// Initialize profiler overlay
profilerOverlay = new ProfilerOverlay(Resources::font);
// Only load game.py if no custom script/command/module/exec is specified
bool should_load_game = config.script_path.empty() &&
@ -85,6 +88,7 @@ GameEngine::~GameEngine()
for (auto& [name, scene] : scenes) {
delete scene;
}
delete profilerOverlay;
}
void GameEngine::cleanup()
@ -199,10 +203,14 @@ void GameEngine::run()
testTimers();
// Update Python scenes
McRFPy_API::updatePythonScenes(frameTime);
{
ScopedTimer pyTimer(metrics.pythonScriptTime);
McRFPy_API::updatePythonScenes(frameTime);
}
// Update animations (only if frameTime is valid)
if (frameTime > 0.0f && frameTime < 1.0f) {
ScopedTimer animTimer(metrics.animationTime);
AnimationManager::getInstance().update(frameTime);
}
@ -240,6 +248,12 @@ void GameEngine::run()
currentScene()->render();
}
// Update and render profiler overlay (if enabled)
if (profilerOverlay && !headless) {
profilerOverlay->update(metrics);
profilerOverlay->render(*render_target);
}
// Display the frame
if (headless) {
headless_renderer->display();
@ -330,6 +344,14 @@ void GameEngine::processEvent(const sf::Event& event)
int actionCode = 0;
if (event.type == sf::Event::Closed) { running = false; return; }
// Handle F3 for profiler overlay toggle
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::F3) {
if (profilerOverlay) {
profilerOverlay->toggle();
}
return;
}
// Handle window resize events
else if (event.type == sf::Event::Resized) {
// Update the viewport to handle the new window size