updated using numpy

debugger_system
This commit is contained in:
Stan44 2024-12-29 19:29:37 -06:00
parent dac5fa2213
commit c6c047fdb7
5 changed files with 189 additions and 134 deletions

View File

@ -22,7 +22,7 @@ import time
import numpy as np
engine_settings = {
'pause_sim': True,
'pause_sim': False,
'enable_cursor': True,
'enable_glow': False,
'enable_gas_effect': True,
@ -76,5 +76,5 @@ def load_particle_properties():
# Load particle properties once when module is imported
particle_properties = load_particle_properties()
__all__ = ['pygame', 'np', 'random', 'time', 'engine_settings', 'particle_properties', 'cProfile', 'pstats', 'sys', 'os']
__all__ = ['pygame', 'np', 'random', 'time', 'engine_settings', 'particle_properties', 'cProfile', 'pstats', 'sys', 'os', 'jit']

View File

@ -0,0 +1,66 @@
from config.settings import engine_settings, pygame
import time
class DebuggerSystem:
def __init__(self):
self.debug_surface = pygame.Surface((300, 150), pygame.SRCALPHA)
self.debug_font = pygame.font.SysFont(None, 24)
self.fps_counter = 0
self.fps_timer = time.time()
self.current_fps = 0
self.debug_text = []
self.performance_metrics = {
'particle_updates': 0,
'physics_time': 0,
'render_time': 0
}
def track_fps(self):
"""Track FPS with high precision"""
self.fps_counter += 1
current_time = time.time()
elapsed = current_time - self.fps_timer
if elapsed >= 1.0:
self.current_fps = self.fps_counter / elapsed
self.fps_counter = 0
self.fps_timer = current_time
return self.current_fps
def update_performance_metrics(self, sim):
"""Track simulation performance metrics"""
self.performance_metrics['particle_updates'] = len(sim.active_particles)
return self.performance_metrics
def draw_debug_overlay(self, screen, sim):
if not engine_settings['enable_fps'] and not engine_settings['enable_debug']:
return
self.debug_surface.fill((0, 0, 0, 128))
y_offset = 10
if engine_settings['enable_fps']:
fps_text = f"FPS: {self.current_fps:.1f} | TPS: {sim.track_tps():.1f}"
fps_surf = self.debug_font.render(fps_text, True, (255, 255, 255))
self.debug_surface.blit(fps_surf, (10, y_offset))
y_offset += 25
if engine_settings['enable_debug']:
debug_info = [
f"Active Particles: {len(sim.active_particles)}",
f"Total Particles: {sim.particle_count}",
f"Current Type: {sim.current_particle_type}",
f"Brush Size: {sim.brush_size}"
]
for line in debug_info:
text_surf = self.debug_font.render(line, True, (255, 255, 255))
self.debug_surface.blit(text_surf, (10, y_offset))
y_offset += 25
screen.blit(self.debug_surface, (0, 0))
def log_error(self, error_msg):
"""Log errors for debugging"""
with open('debug_log.txt', 'a') as f:
f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_msg}\n")

View File

@ -19,7 +19,7 @@ Key Components:
"""
#Load the imports.
from config.settings import random, time, particle_properties
from config.settings import np, time, particle_properties
# Load particle properties from json so we know what particles we got and how they should be simulated.
@ -112,15 +112,26 @@ class Simulation:
self.max_brush_size = 20
self.particle_properties = particle_properties
self.particle_types = list(self.particle_properties.keys())
self.current_particle_type = self.particle_types[0] if self.particle_types else 'sand'
self.gravity = 9.8 # m/s^2, adjustable based on the scale of simulation
self.wind_zones = []
self.wind = [0.0, 0.0] # Global wind vector (x, y)
def reset_particle_count(self):
self.particle_count = 0
"""Reset and recalculate accurate particle count"""
active_count = np.sum([1 for x, y in self.active_particles if self.particles[x][y] is not None])
self.particle_count = int(active_count)
def get_accurate_particle_count(self):
"""Get current accurate particle count using numpy"""
particle_mask = np.array([[self.particles[x][y] is not None
for y in range(self.height)]
for x in range(self.width)])
return np.sum(particle_mask)
def get_cell_key(self, x, y): # this is where we get the cell key.
# Convert coordinates to grid cell
cell_x = x // self.cell_size
@ -238,7 +249,9 @@ class Simulation:
return
# Pressure damage
pressure = self.calculate_forces(particle, x, y)
fx, fy = self.calculate_forces(particle, x, y)
pressure = (fx*fx + fy*fy)**0.5 # Calculate magnitude of force vector
if hasattr(particle, 'pressure_threshold') and pressure > particle.pressure_threshold:
particle.durability -= 0.1
@ -299,7 +312,7 @@ class Simulation:
# Wood to Fire
elif target.particle_type == 'wood':
if random.random() < 0.3: # 30% chance to ignite
if np.random.random() < 0.3: # 30% chance to ignite
self.transform_particle(target_x, target_y, 'fire')
@ -324,8 +337,8 @@ class Simulation:
def handle_gas_movement(self, particle, x, y): # this is where we handle the gas movement. this function sucks. wip
"""Handle gas particle movement"""
if particle.is_gas:
dx = random.uniform(-1, 1)
dy = random.uniform(-2, 0) # Bias upward movement
dx = np.random.uniform(-1, 1)
dy = np.random.uniform(-2, 0) # Bias upward movement
new_x = int(x + dx)
new_y = int(y + dy)
@ -349,43 +362,55 @@ class Simulation:
self.wind_zones.append(wind_zone)
def calculate_forces(self, particle, x, y): # this is where we calculate the forces.
def calculate_forces(self, particle, x, y):
"""Calculate net forces acting on a particle."""
fx, fy = 0.0, 0.0 # Initialize forces
# Initialize forces as numpy array for vectorized operations
forces = np.zeros(2, dtype=np.float32) # [fx, fy]
# Check wind zones
for zone in self.wind_zones:
dx = x - zone['x']
dy = y - zone['y']
distance = (dx*dx + dy*dy) ** 0.5
if distance <= zone['radius']:
fx += zone['direction'][0] * zone['strength'] * (1 - distance/zone['radius'])
fy += zone['direction'][1] * zone['strength'] * (1 - distance/zone['radius'])
# Vectorized wind zone calculations
if self.wind_zones:
positions = np.array([[zone['x'], zone['y']] for zone in self.wind_zones])
directions = np.array([zone['direction'] for zone in self.wind_zones])
strengths = np.array([zone['strength'] for zone in self.wind_zones])
radii = np.array([zone['radius'] for zone in self.wind_zones])
# Calculate distances vectorized
dx = x - positions[:, 0]
dy = y - positions[:, 1]
distances = np.sqrt(dx**2 + dy**2)
# Apply wind zone forces where distance <= radius
mask = distances <= radii
scale_factors = np.where(mask, (1 - distances/radii) * strengths[:, np.newaxis], 0)
forces += np.sum(directions * scale_factors[:, np.newaxis], axis=0)
# Apply wind force
if particle.is_gas:
fx += self.wind[0] * 0.5
fy += self.wind[1] * 0.5
else:
fx += self.wind[0]
fy += self.wind[1]
# Apply global wind with vectorized operation
wind_factor = 0.5 if particle.is_gas else 1.0
forces += np.array(self.wind) * wind_factor
# Apply drag force
# Apply drag force vectorized
drag = particle.viscosity * -1
fx += drag * particle.velocity[0]
fy += drag * particle.velocity[1]
forces += drag * np.array(particle.velocity)
# Check neighboring particles
neighbors = self._get_neighbors_from_grid(x, y)
for nx, ny in neighbors:
if(nx, ny) != (x, y):
if 0 <= nx < self.width and 0 <= ny < self.height:
# Neighbor forces using numpy arrays
neighbors = np.array(self._get_neighbors_from_grid(x, y))
if len(neighbors):
valid_mask = (
(neighbors[:, 0] >= 0) &
(neighbors[:, 0] < self.width) &
(neighbors[:, 1] >= 0) &
(neighbors[:, 1] < self.height)
)
neighbors = neighbors[valid_mask]
for nx, ny in neighbors:
if (nx, ny) != (x, y):
neighbor = self.particles[nx][ny]
if neighbor:
self._apply_neighbor_forces(particle, neighbor, fx, fy)
self._apply_neighbor_forces(particle, neighbor, forces[0], forces[1])
return fx, fy
return forces[0], forces[1]
def _get_quick_neighbors(self, x, y):
@ -426,23 +451,23 @@ class Simulation:
if neighbor and hasattr(neighbor, 'flamability'):
if neighbor.particle_type == 'wood':
# Higher chance to ignite wood
if random.random() < 0.3: # 30% chance to spread
if np.random.random() < 0.3: # 30% chance to spread
self.ignite_particle(neighbor)
elif neighbor.flamability > 0:
if random.random() < 0.1: # 10% chance for other materials
if np.random.random() < 0.1: # 10% chance for other materials
self.ignite_particle(neighbor)
def handle_special_particles(self, particle, x, y): # this is where we handle special particles.
"""Handle special particle behaviors"""
if particle.particle_type in ['fire', 'flame', 'smoke']:
if random.random() < 0.6: # % chance
if np.random.random() < 0.6: # % chance
self.particles[x][y] = None
self.active_particles.discard((x, y))
if particle.particle_type in ['fire', 'flame', 'lava']:
# Create smoke above with proper physics
if random.random() < 0.65 and y > 0: # % chance for smoke
if np.random.random() < 0.65 and y > 0: # % chance for smoke
properties = self.particle_properties['smoke']
new_smoke = Particle.from_type((x, y-1), 'smoke', properties)
if self.particles[x][y-1] is None:
@ -474,7 +499,7 @@ class Simulation:
# Transition to gas
particle.is_gas = True
particle.temperature = 1700
particle.velocity = [random.uniform(-1, 1), random.uniform(-1, 1)]
particle.velocity = [np.random.uniform(-1, 1), np.random.uniform(-1, 1)]
particle.temperature < 1400
particle.is_gas = False
@ -511,7 +536,10 @@ class Simulation:
grid_x = x // self.particle_size
grid_y = y // self.particle_size
if 0 <= grid_x < self.width and 0 <= grid_y < self.height:
if (0 <= grid_x < self.width and
0 <= grid_y < self.height and
self.particles[grid_x][grid_y] is None):
properties = self.particle_properties[particle_type]
position = (grid_x, grid_y)
new_particle = Particle(
@ -521,11 +549,10 @@ class Simulation:
particle_type=particle_type,
properties=properties
)
# Add to the grid
if 0 <= grid_x < len(self.particles) and 0 <= grid_y < len(self.particles[0]):
self.particles[grid_x][grid_y] = new_particle
self.active_particles.add((grid_x, grid_y))
self.particle_count += 1
self.particles[grid_x][grid_y] = new_particle
self.active_particles.add((grid_x, grid_y))
self.particle_count += 1
def create_particle_circle(self, center_x, center_y): # this is where we create the particle circle.
@ -544,10 +571,10 @@ class Simulation:
return None
def apply_gravity(self, dt): # this is where we apply gravity.
def apply_gravity(self, dt):
"""Handle only gravity and basic particle movement"""
self.spatial_grid.clear()
for x, y in list(self.active_particles):
particle = self.particles[x][y]
if not particle or particle.particle_type in ['wall', 'stone', 'wood']:
@ -566,15 +593,18 @@ class Simulation:
if self.particles[x][new_y] is None:
new_x, new_y = x, y + 1
else:
# Try diagonal movement with randomization
# Define diagonal directions first
diagonal_dirs = [(-1, 1), (1, 1)]
random.shuffle(diagonal_dirs)
diagonal_dirs = np.array(diagonal_dirs)
# Randomize movement directions
diagonal_dirs = np.random.permutation(diagonal_dirs)
for dx, dy in diagonal_dirs:
test_x = x + dx
test_y = y + dy
if (0 <= test_x < self.width and 0 <= test_y < self.height and
self.particles[test_x][test_y] is None):
if random.random() < 0.8: # 80% chance to move diagonally
if np.random.random() < 0.8: # 80% chance to move diagonally
new_x = test_x
new_y = test_y
break
@ -585,8 +615,8 @@ class Simulation:
new_x = x
new_y = y + 1
else:
spread_directions = [(-1, 0), (1, 0)]
random.shuffle(spread_directions)
spread_directions = np.array([(-1, 0), (1, 0)])
np.random.shuffle(spread_directions)
for dx, _ in spread_directions:
test_x = x + dx
if (0 <= test_x < self.width and
@ -604,8 +634,7 @@ class Simulation:
particle.position = (new_x, new_y)
def apply_physics(self, dt, engine_settings): # this is where we apply physics.
def apply_physics(self, dt, engine_settings):
"""Handle all physics effects"""
new_active_particles = set()
updates = []
@ -615,8 +644,6 @@ class Simulation:
if not particle:
continue
# Skip wall physics - walls are immutable
if particle.particle_type == 'wall':
new_active_particles.add((x, y))
@ -628,9 +655,9 @@ class Simulation:
self.particles[x][y] = None
self.active_particles.discard((x, y))
# Handle fire movement
dx = random.uniform(-0.5, 0.5)
dy = random.uniform(-1.5, -0.5) # Upward drift
# Handle fire movement using numpy random
dx = np.random.uniform(-0.5, 0.5)
dy = np.random.uniform(-1.5, -0.5) # Upward drift
new_x = int(x + dx)
new_y = int(y + dy)
@ -639,12 +666,12 @@ class Simulation:
self.particles[new_x][new_y] = particle
new_active_particles.add((new_x, new_y))
# Generate smoke above the new fire position
if random.random() < 0.25 and new_y > 0:
# Generate smoke above with numpy random
if np.random.random() < 0.25 and new_y > 0:
properties = self.particle_properties['smoke']
new_smoke = Particle(
position=(new_x, new_y-1),
velocity=[random.uniform(-0.5, 0.5), -1],
velocity=[np.random.uniform(-0.5, 0.5), -1],
mass=properties.get('mass', 0.1),
particle_type='smoke',
properties=properties
@ -653,13 +680,12 @@ class Simulation:
self.particles[new_x][new_y-1] = new_smoke
new_active_particles.add((new_x, new_y-1))
# Dissipation chance
if random.random() < 0.02:
# Dissipation chance using numpy random
if np.random.random() < 0.02:
continue
continue
# Air handling - particles can pass through should implement proper air instead of a particle
# Air handling
if particle.particle_type == 'air':
continue
@ -669,17 +695,17 @@ class Simulation:
# Calculate forces
fx, fy = self.calculate_forces(particle, x, y)
# handle gas particles
# Handle gas particles
if particle.is_gas:
# Gas-specific movement
dx = random.uniform(2, -1)
dy = random.uniform(-2, 0) # Bias upward
# Gas movement with numpy random
dx = np.random.uniform(2, -1)
dy = np.random.uniform(-2, 0) # Bias upward
new_x = int(x + dx)
new_y = int(y + dy)
self.particles[x][y] = None
self.active_particles.discard((x, y))
# Check if the new position is within bounds
if 0 <= new_x < self.width and 0 <= new_y < self.height:
if self.particles[new_x][new_y] is None:
self.particles[x][y] = None
@ -694,10 +720,9 @@ class Simulation:
particle.velocity[1] += (fy / mass) * dt
if particle.liquid:
# Enhanced liquid spreading
spread_chance = 0.5
if random.random() < spread_chance:
dx = random.choice([-1, 1])
# Enhanced liquid spreading with numpy random
if np.random.random() < 0.5:
dx = np.random.choice([-1, 1])
if (0 <= x + dx < self.width and
self.particles[x + dx][y] is None):
new_x = x + dx
@ -715,33 +740,29 @@ class Simulation:
if self.particles[new_x][new_y] is None:
updates.append((x, y, new_x, new_y, particle))
new_active_particles.add((new_x, new_y))
# Wake up neighboring dormant particles
self._wake_neighbors(new_x, new_y)
else:
new_active_particles.add((x, y))
# Apply updates and return new active set
# Apply updates
for old_x, old_y, new_x, new_y, particle in updates:
self.particles[old_x][old_y] = None
self.particles[new_x][new_y] = particle
particle.position = (new_x, new_y)
# Handle boundaries based on settings
# Handle boundaries
if engine_settings['outerwall']:
if x <= 0 or x >= self.width-1 or y <= 0 or y >= self.height-1:
# Create wall particle at boundary if none exists
if self.particles[x][y] is None:
properties = self.particle_properties['wall']
wall = Particle.from_type((x, y), 'wall', properties)
self.particles[x][y] = wall
new_active_particles.add((x, y))
self.particle_count += 1 # Track new wall particle
self.particle_count += 1
continue
else:
# Delete particles that go out of bounds
if x <= 0 or x >= self.width-1 or y <= 0 or y >= self.height-1:
if self.particles[x][y] is not None:
if self.particles[x][y] is not None:
self.particles[x][y] = None
self.active_particles.discard((x, y))
self.particle_count -= 1

View File

@ -23,6 +23,7 @@ The `clear_screen` function is used to reset the simulation grid and clear the d
from config.settings import pygame, random, particle_properties, engine_settings
from debug.debugger_system import DebuggerSystem
class Rendering:
@ -48,6 +49,7 @@ class Rendering:
'zoom_window': pygame.font.SysFont(None, 20),
'zoom_window_text': pygame.font.SysFont(None, 20)
}
self.debug = DebuggerSystem()
# Pre-render static UI elements
self.button_surfaces = {}
@ -284,52 +286,13 @@ class Rendering:
def draw_debug_overlay(self, fps, sim):
if not engine_settings['enable_fps'] and not engine_settings['enable_debug']:
return
# Create static debug surface if not exists
if not hasattr(self, 'debug_surface'):
self.debug_surface = pygame.Surface((300, 150), pygame.SRCALPHA)
# Only update when values change significantly
mouse_x, mouse_y = pygame.mouse.get_pos()
grid_x, grid_y = mouse_x // 3, mouse_y // 3
current_info = (
int(fps),
int(sim.track_tps()),
mouse_x // 10, # Reduced update frequency
mouse_y // 10,
sim.particle_count
)
if not hasattr(self, '_last_debug_info') or current_info != self._last_debug_info:
self._last_debug_info = current_info
self.debug_surface.fill((0, 0, 0, 0))
font = self.cached_fonts['debug']
y_offset = 10
if engine_settings['enable_fps']:
fps_surf = font.render(f"FPS: {fps:.1f} | TPS: {sim.track_tps():.1f}", True, (255, 255, 255))
self.debug_surface.blit(fps_surf, (10, y_offset))
y_offset += 25
if engine_settings['enable_debug']:
debug_lines = [
f"Mouse: ({mouse_x}, {mouse_y})",
f"Grid: ({grid_x}, {grid_y})",
f"Particles: {sim.particle_count}"
]
for line in debug_lines:
text_surf = font.render(line, True, (255, 255, 255))
self.debug_surface.blit(text_surf, (10, y_offset))
y_offset += 25
# Single blit of cached surface
self.screen.blit(self.debug_surface, (0, 0))
"""Draws debug overlay on the screen."""
if engine_settings['enable_fps'] or engine_settings['enable_debug']:
self.debug.screen = self.screen # Pass screen reference
self.debug.cached_fonts = self.cached_fonts # Pass font references
self.debug.track_fps()
self.debug.update_performance_metrics(sim)
self.debug.draw_debug_overlay(self.screen, sim)
def draw_buttons(self): # this is the function that draws the buttons
self.buttons = {}

View File

@ -15,7 +15,7 @@ The main loop runs at a target frame rate of 60 FPS (this fps varies on my mood
"""
# Import Require files for the Engine.
from config.settings import pygame, engine_settings, cProfile, pstats
from config.settings import pygame, engine_settings, cProfile, pstats, time
from rendering.rendering import Rendering
from physics.sim import Simulation
@ -274,9 +274,14 @@ def main():
if mouse_down_left and not over_button:
x, y = mouse_pos
if sim.current_particle_type not in ['wind', 'air']:
current_time = time.time()
if not hasattr(main, 'last_particle_time'):
main.last_particle_time = 0
# Limit particle creation to every 16ms (approximately 60 FPS)
if current_time - main.last_particle_time >= 0.016:
sim.create_particle_circle(x, y)
else:
sim.add_wind_zone(x, y)
main.last_particle_time = current_time
if mouse_down_right:
x, y = mouse_pos