Add progress bar for simulation tracking and file writing

This commit is contained in:
Sam 2025-11-08 23:15:24 -06:00
parent b7e4c96188
commit 0ce010a12d

View File

@ -12,6 +12,11 @@ from output import MetricsCollector, EntityCollector, EvolutionCollector
from output.formatters.json_formatter import JSONFormatter from output.formatters.json_formatter import JSONFormatter
from output.formatters.csv_formatter import CSVFormatter from output.formatters.csv_formatter import CSVFormatter
from output.writers.file_writer import FileWriter from output.writers.file_writer import FileWriter
try:
from tqdm import tqdm
TQDM_AVAILABLE = True
except ImportError:
TQDM_AVAILABLE = False
@dataclass @dataclass
@ -56,6 +61,12 @@ class HeadlessSimulationEngine:
'evolution': [] 'evolution': []
} }
# Progress tracking
self.files_written = 0
self.last_progress_update = 0
self.progress_update_interval = 1.0 # Update progress every second
self.progress_bar = None
# Setup signal handlers for graceful shutdown # Setup signal handlers for graceful shutdown
signal.signal(signal.SIGINT, self._signal_handler) signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler) signal.signal(signal.SIGTERM, self._signal_handler)
@ -88,6 +99,120 @@ class HeadlessSimulationEngine:
return collectors return collectors
def _init_progress_bar(self):
"""Initialize progress bar for simulation."""
if not TQDM_AVAILABLE:
return
# Determine progress total based on configuration
if self.config.max_ticks:
total = self.config.max_ticks
unit = 'ticks'
elif self.config.max_duration:
total = int(self.config.max_duration)
unit = 'sec'
else:
# No clear total - create indeterminate progress bar
total = None
unit = 'ticks'
if total:
self.progress_bar = tqdm(
total=total,
unit=unit,
desc="Simulation",
leave=True, # Keep the bar when done
bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]'
)
else:
self.progress_bar = tqdm(
unit='ticks',
desc="Simulation",
leave=True,
bar_format='{l_bar}{bar}| {n_fmt} [{elapsed}, {rate_fmt}]'
)
def _update_progress_bar(self):
"""Update progress bar with current status."""
current_time = time.time()
if current_time - self.last_progress_update < self.progress_update_interval:
return
current_tick = self.simulation_core.state.total_ticks
tps = self.simulation_core.state.actual_tps
elapsed = current_time - self.start_time
if TQDM_AVAILABLE and self.progress_bar:
# Use tqdm progress bar
if self.config.max_ticks:
# Update based on tick progress
progress = min(current_tick, self.config.max_ticks)
self.progress_bar.n = progress
self.progress_bar.set_postfix({
'TPS': f'{tps:.1f}',
'Files': self.files_written
})
elif self.config.max_duration:
# Update based on elapsed time
progress = min(elapsed, self.config.max_duration)
self.progress_bar.n = int(progress)
self.progress_bar.set_postfix({
'TPS': f'{tps:.1f}',
'Files': self.files_written,
'Tick': current_tick
})
else:
# Indeterminate progress
self.progress_bar.n = current_tick
self.progress_bar.set_postfix({
'TPS': f'{tps:.1f}',
'Files': self.files_written
})
self.progress_bar.refresh()
else:
# Simple text-based progress
eta_text = ""
if self.config.max_ticks and current_tick > 0:
tick_rate = current_tick / elapsed if elapsed > 0 else 0
remaining_ticks = self.config.max_ticks - current_tick
eta_seconds = remaining_ticks / tick_rate if tick_rate > 0 else 0
eta_minutes, eta_seconds = divmod(eta_seconds, 60)
eta_text = f"ETA: {int(eta_minutes)}m{int(eta_seconds)}s"
elif self.config.max_duration:
remaining_seconds = self.config.max_duration - elapsed
eta_minutes, eta_seconds = divmod(remaining_seconds, 60)
eta_text = f"ETA: {int(eta_minutes)}m{int(eta_seconds)}s"
# Calculate progress percentage if we have a limit
progress_pct = ""
if self.config.max_ticks:
pct = (current_tick / self.config.max_ticks) * 100
progress_pct = f"{pct:.1f}%"
elif self.config.max_duration:
pct = (elapsed / self.config.max_duration) * 100
progress_pct = f"{pct:.1f}%"
progress_line = f"[{current_time - self.start_time:.1f}s] "
if progress_pct:
progress_line += f"Progress: {progress_pct} "
progress_line += f"Tick: {current_tick} TPS: {tps:.1f} Files: {self.files_written}"
if eta_text:
progress_line += f" {eta_text}"
# Overwrite the previous line (using carriage return)
print(f"\r{progress_line}", end="", flush=True)
self.last_progress_update = current_time
def _close_progress_bar(self):
"""Close the progress bar."""
if not TQDM_AVAILABLE and self.running:
# Print a newline to clear the text progress line
print()
elif TQDM_AVAILABLE and self.progress_bar:
self.progress_bar.close()
def run(self) -> Dict[str, Any]: def run(self) -> Dict[str, Any]:
"""Run the headless simulation.""" """Run the headless simulation."""
# Determine if we should run at max speed # Determine if we should run at max speed
@ -107,6 +232,9 @@ class HeadlessSimulationEngine:
self.start_time = time.time() self.start_time = time.time()
self.simulation_core.start() self.simulation_core.start()
# Initialize progress bar
self._init_progress_bar()
# Enable sprint mode for maximum speed if not real-time mode # Enable sprint mode for maximum speed if not real-time mode
if max_speed_mode: if max_speed_mode:
self.simulation_core.timing.set_sprint_mode(True) self.simulation_core.timing.set_sprint_mode(True)
@ -137,6 +265,9 @@ class HeadlessSimulationEngine:
self._write_batch_data() self._write_batch_data()
last_batch_time = time.time() last_batch_time = time.time()
# Update progress bar
self._update_progress_bar()
# Real-time delay if needed # Real-time delay if needed
if self.config.real_time: if self.config.real_time:
time.sleep(0.016) # ~60 FPS time.sleep(0.016) # ~60 FPS
@ -206,6 +337,7 @@ class HeadlessSimulationEngine:
} }
formatted_data = formatter.format(combined_data) formatted_data = formatter.format(combined_data)
self.file_writer.write(formatted_data, filename) self.file_writer.write(formatted_data, filename)
self.files_written += 1
# Clear written data # Clear written data
data_list.clear() data_list.clear()
@ -214,6 +346,10 @@ class HeadlessSimulationEngine:
def _finalize(self): def _finalize(self):
"""Finalize simulation and write remaining data.""" """Finalize simulation and write remaining data."""
# Close progress bar
self._close_progress_bar()
print("Finalizing simulation...") print("Finalizing simulation...")
# Write any remaining data # Write any remaining data
@ -224,12 +360,14 @@ class HeadlessSimulationEngine:
if 'json' in self.formatters: if 'json' in self.formatters:
summary_data = self.formatters['json'].format(summary) summary_data = self.formatters['json'].format(summary)
self.file_writer.write(summary_data, "simulation_summary.json") self.file_writer.write(summary_data, "simulation_summary.json")
self.files_written += 1
# Stop simulation # Stop simulation
self.simulation_core.stop() self.simulation_core.stop()
self.file_writer.close() self.file_writer.close()
print("Simulation completed") print("Simulation completed")
print(f"Total files written: {self.files_written}")
def _get_summary(self) -> Dict[str, Any]: def _get_summary(self) -> Dict[str, Any]:
"""Get simulation summary.""" """Get simulation summary."""