DynamicAbstractionSystem/core/simulation_engine.py

345 lines
13 KiB
Python

import pygame
import time
import sys
from pygame_gui import UIManager
from world.simulation_interface import Camera
from config.constants import *
from core.input_handler import InputHandler
from core.renderer import Renderer
from core.simulation_core import SimulationCore, SimulationConfig
from core.event_bus import EventBus
from ui.hud import HUD
import cProfile
import pstats
class SimulationEngine:
"""Interactive simulation engine with UI (wrapper around SimulationCore)."""
def __init__(self):
pygame.init()
self.event_bus = EventBus()
self._init_window()
self._init_simulation()
self._init_ui()
self.running = True
# HUD action handlers registry for extensibility
self._hud_action_handlers = {
'toggle_pause': self.simulation_core.toggle_pause,
'step_forward': self.simulation_core.step,
'toggle_sprint': self.simulation_core.toggle_sprint_mode,
'viewport_resized': self._update_simulation_view,
'set_speed': self.simulation_core.set_speed_multiplier,
'set_custom_tps': self.simulation_core.set_tps,
}
def _profile_single_tick(self):
"""Profile a single tick for performance analysis."""
profiler = cProfile.Profile()
profiler.enable()
self.simulation_core.world.tick_all()
profiler.disable()
profiler.dump_stats('profile_tick.prof') # Save to file
def _init_window(self):
info = pygame.display.Info()
self.window_width = int(info.current_w // 1.5)
self.window_height = int(info.current_h // 1.5)
self.screen = pygame.display.set_mode(
(self.window_width, self.window_height),
pygame.RESIZABLE, vsync=1
)
pygame.display.set_caption("Dynamic Abstraction System Testing")
self.clock = pygame.time.Clock()
def _init_ui(self):
self.ui_manager = UIManager((self.window_width, self.window_height))
self.hud = HUD(self.ui_manager, self.window_width, self.window_height)
self.hud.update_layout(self.window_width, self.window_height)
self._update_simulation_view()
def _init_simulation(self):
# Initialize default sim view rect (will be updated by _init_ui)
self.sim_view_width = self.window_width - 400 # Rough estimate for inspector width
self.sim_view_height = self.window_height - 200 # Rough estimate for control bar height
self.sim_view = pygame.Surface((self.sim_view_width, self.sim_view_height))
self.sim_view_rect = self.sim_view.get_rect(topleft=(200, 48)) # Rough estimate
# Create simulation core
sim_config = SimulationConfig(
grid_width=GRID_WIDTH,
grid_height=GRID_HEIGHT,
cell_size=CELL_SIZE,
initial_cells=50,
initial_food=FOOD_OBJECTS_COUNT,
food_spawning=FOOD_SPAWNING,
random_seed=RANDOM_SEED,
default_tps=DEFAULT_TPS
)
self.simulation_core = SimulationCore(sim_config, self.event_bus)
# Setup input handler with simulation core world
self.input_handler = InputHandler(
self.simulation_core.camera,
self.simulation_core.world,
self.sim_view_rect
)
self.input_handler.tps = self.simulation_core.timing.state.tps
self.input_handler.default_tps = DEFAULT_TPS
# Set up action callbacks for input handler
self._setup_input_handler_callbacks()
# Setup renderer
self.renderer = Renderer(self.sim_view)
# Profile a single tick for performance analysis
self._profile_single_tick()
def _update_simulation_view(self):
viewport_rect = self.hud.get_viewport_rect()
self.sim_view_width = viewport_rect.width
self.sim_view_height = viewport_rect.height
self.sim_view = pygame.Surface((self.sim_view_width, self.sim_view_height))
self.sim_view_rect = self.sim_view.get_rect(topleft=(viewport_rect.left, viewport_rect.top))
self.ui_manager.set_window_resolution((self.window_width, self.window_height))
self.renderer = Renderer(self.sim_view)
# Update simulation core camera dimensions
self.simulation_core.camera.screen_width = self.sim_view_width
self.simulation_core.camera.screen_height = self.sim_view_height
# Update input handler simulation view rect
self.input_handler.update_sim_view_rect(self.sim_view_rect)
def _setup_input_handler_callbacks(self):
"""Set up action callbacks for input handler."""
callbacks = {
'toggle_pause': self.simulation_core.toggle_pause,
'step_forward': self.simulation_core.step,
'set_speed': self.simulation_core.set_speed_multiplier,
'set_custom_tps': self.simulation_core.set_tps,
'toggle_sprint': self.simulation_core.toggle_sprint_mode,
}
for action, callback in callbacks.items():
self.input_handler.set_action_callback(action, callback)
def _count_cells(self):
"""Count cells in the simulation."""
# Import locally to avoid circular import
from world.objects import DefaultCell
return self.simulation_core.count_entities_by_type(DefaultCell)
def run(self):
"""Run the interactive simulation engine."""
print(f"World buffer: {self.simulation_core.world.current_buffer}")
self.simulation_core.start()
while self.running:
self._handle_frame()
self.simulation_core.stop()
pygame.quit()
sys.exit()
def _handle_frame(self):
"""Handle a single frame in the interactive simulation."""
deltatime = self.clock.get_time() / 1000.0
# Handle events
events = pygame.event.get()
self.running = self.input_handler.handle_events(events, self.hud.manager)
# Process HUD events and window events
for event in events:
hud_action = self.hud.process_event(event)
self._process_hud_action(hud_action)
if event.type == pygame.VIDEORESIZE:
self._handle_window_resize(event)
# Sync input handler state with simulation core timing
self._sync_input_and_timing()
# Handle sprint mode
if self.input_handler.sprint_mode:
self._handle_sprint_mode()
return
# Update UI manager every frame
self.hud.manager.update(deltatime)
# Handle step-forward mode
if self.input_handler.is_stepping:
self.simulation_core.step()
self.input_handler.is_stepping = False
else:
# Update simulation using core
self.simulation_core.update(deltatime)
# Update selected objects in input handler
self.input_handler.update_selected_objects()
# Render frame
self._update_frame(deltatime)
self._render_frame()
def _sync_input_and_timing(self):
"""Synchronize input handler state with simulation core timing."""
timing_state = self.simulation_core.timing.state
# Sync TPS
self.input_handler.tps = timing_state.tps
# Sync pause state
self.input_handler.is_paused = timing_state.is_paused
# Sync sprint mode
self.input_handler.sprint_mode = timing_state.sprint_mode
# Sync speed multiplier
self.input_handler.speed_multiplier = timing_state.speed_multiplier
def _update_frame(self, deltatime):
"""Update camera and input state."""
keys = pygame.key.get_pressed()
self.input_handler.update_camera(keys, deltatime)
def _render_frame(self):
"""Render the complete frame."""
self.screen.fill(BLACK)
self.renderer.clear_screen()
# Render simulation world
self._render_simulation_world()
# Update and render UI
self._update_and_render_ui()
# Render HUD overlays
self._render_hud_overlays()
pygame.display.flip()
self.clock.tick(MAX_FPS)
def _render_simulation_world(self):
"""Render the simulation world if not dragging splitter."""
if not self.hud.dragging_splitter:
self.renderer.draw_grid(self.simulation_core.camera, self.input_handler.show_grid)
self.renderer.render_world(self.simulation_core.world, self.simulation_core.camera)
self.renderer.render_interaction_radius(
self.simulation_core.world,
self.simulation_core.camera,
self.input_handler.selected_objects,
self.input_handler.show_interaction_radius
)
self.renderer.render_selection_rectangle(
self.input_handler.get_selection_rect(),
self.sim_view_rect
)
self.renderer.render_selected_objects_outline(
self.input_handler.selected_objects,
self.simulation_core.camera
)
self.screen.blit(self.sim_view, (self.sim_view_rect.left, self.sim_view_rect.top))
def _update_and_render_ui(self):
"""Update UI elements and render them."""
# Update HUD displays with simulation core state
self.hud.update_simulation_controls(self.simulation_core)
# Draw UI elements
self.hud.manager.draw_ui(self.screen)
self.hud.draw_splitters(self.screen)
def _render_hud_overlays(self):
"""Render HUD overlay elements."""
self.hud.render_fps(self.screen, self.clock)
self.hud.render_tps(self.screen, self.simulation_core.state.actual_tps)
self.hud.render_selected_objects_info(self.screen, self.input_handler.selected_objects)
self.hud.render_legend(self.screen, self.input_handler.show_legend)
self.hud.render_pause_indicator(self.screen, self.simulation_core.timing.state.is_paused)
# Render neural network visualization for selected object
if self.input_handler.selected_objects:
self.hud.render_neural_network_visualization(
self.screen,
self.input_handler.selected_objects[0]
)
def _handle_window_resize(self, event):
"""Handle window resize event."""
self.window_width, self.window_height = event.w, event.h
self.screen = pygame.display.set_mode(
(self.window_width, self.window_height),
pygame.RESIZABLE
)
self._update_simulation_view()
self.hud.update_layout(self.window_width, self.window_height)
def _process_hud_action(self, action):
"""Process a single HUD action using the handler registry."""
if not action:
return
# Handle simple actions directly
if action in self._hud_action_handlers:
self._hud_action_handlers[action]()
return
# Handle parameterized actions
if isinstance(action, tuple) and len(action) >= 2:
action_type, param = action[0], action[1]
if action_type == 'set_speed':
self.simulation_core.set_speed_multiplier(param)
elif action_type == 'set_custom_tps':
self.simulation_core.set_tps(param)
elif action_type == 'reset_tps_display':
self._reset_tps_display()
def _reset_tps_display(self):
"""Reset TPS display to current simulation value."""
if self.hud.custom_tps_entry:
current_tps = int(self.simulation_core.timing.state.tps)
self.hud.custom_tps_entry.set_text(str(current_tps))
def register_hud_action(self, action_name: str, handler):
"""Register a new HUD action handler for extensibility.
Args:
action_name: Name of the HUD action
handler: Callable that handles the action
"""
self._hud_action_handlers[action_name] = handler
def _handle_sprint_mode(self):
"""Handle sprint mode by running multiple simulation ticks quickly."""
current_time = time.perf_counter()
while time.perf_counter() - current_time < 0.05: # 50ms of sprint
self.simulation_core.update(0.016) # Update simulation
self.input_handler.update_selected_objects()
pygame.event.pump() # Prevent event queue overflow
# Render sprint debug info
self.screen.fill(BLACK)
self.renderer.clear_screen()
cell_count = self._count_cells()
self.hud.render_sprint_debug(
self.screen,
self.simulation_core.state.actual_tps,
self.simulation_core.state.total_ticks,
cell_count
)
pygame.display.flip()
self.clock.tick(MAX_FPS)