# GECS Performance Optimization Guide

> **Make your ECS games run fast and smooth**

This guide shows you how to optimize your GECS-based games for maximum performance. Learn to identify bottlenecks, optimize queries, and design systems that scale.

## 📋 Prerequisites

- Understanding of [Core Concepts](CORE_CONCEPTS.md)
- Familiarity with [Best Practices](BEST_PRACTICES.md)
- A working GECS project to optimize

## 🎯 Performance Fundamentals

### The ECS Performance Model

GECS performance depends on three key factors:

1. **Query Efficiency** - How fast you find entities
2. **Component Access** - How quickly you read/write data
3. **System Design** - How well your logic is organized

Most performance gains come from optimizing these in order of impact.

## 🔍 Profiling Your Game

### Monitor Query Cache Performance

Always profile before optimizing. GECS provides query cache statistics for performance monitoring:

```gdscript
# Main.gd
func _process(delta):
    ECS.process(delta)

    # Print cache performance stats every second
    if Engine.get_process_frames() % 60 == 0:
        var cache_stats = ECS.world.get_cache_stats()
        print("ECS Performance:")
        print("  Query cache hits: ", cache_stats.get("hits", 0))
        print("  Query cache misses: ", cache_stats.get("misses", 0))
        print("  Total entities: ", ECS.world.entities.size())

        # Reset stats for next measurement period
        ECS.world.reset_cache_stats()
```

### Use Godot's Built-in Profiler

Monitor your game's performance in the Godot editor:

1. **Run your project** in debug mode
2. **Open the Profiler** (Debug → Profiler)
3. **Look for ECS-related spikes** in the frame time
4. **Identify the slowest systems** in your processing groups

## ⚡ Query Optimization

### 1. Choose the Right Query Method ⭐ NEW!

**As of v5.0.0-rc4**, query performance ranking (10,000 entities):

1. **`.enabled(true/false)` queries**: **~0.05ms** 🏆 **(Fastest - Use when possible!)**
2. **`.with_all([Components])` queries**: **~0.6ms** 🥈 **(Excellent for most use cases)**
3. **`.with_any([Components])` queries**: **~5.6ms** 🥉 **(Good for OR-style queries)**
4. **`.with_group("name")` queries**: **~16ms** 🐌 **(Avoid for performance-critical code)**

**Performance Recommendations:**

```gdscript
# 🏆 FASTEST - Use enabled/disabled queries when you only need active entities
class_name ActiveSystemsOnly extends System
func query():
    return q.enabled(true)  # Constant-time O(1) performance!

# 🥈 EXCELLENT - Component-based queries (heavily optimized cache)
class_name MovementSystem extends System
func query():
    return q.with_all([C_Position, C_Velocity])  # ~0.6ms for 10K entities

# 🥉 GOOD - Use with_any sparingly, split into multiple systems when possible
class_name DamageableSystem extends System
func query():
    return q.with_any([C_Player, C_Enemy]).with_all([C_Health])

# 🐌 AVOID - Group queries are the slowest
class_name PlayerSystem extends System
func query():
    return q.with_group("player")  # Consider using components instead
    # Better: q.with_all([C_Player])
```

### 2. Use Proper System Query Pattern

GECS automatically handles query optimization when you follow the standard pattern:

### 2. Use Proper System Query Pattern

GECS automatically handles query optimization when you follow the standard pattern:

```gdscript
# ✅ Good - Standard GECS pattern (automatically optimized)
class_name MovementSystem extends System

func query():
    return q.with_all([C_Position, C_Velocity]).with_none([C_Frozen])

func process(entities: Array[Entity], components: Array, delta: float):
    # Process each entity
    for entity in entities:
        var pos = entity.get_component(C_Position)
        var vel = entity.get_component(C_Velocity)
        pos.value += vel.value * delta
```

```gdscript
# ❌ Avoid - Manual query building in process methods
func process(entities: Array[Entity], components: Array, delta: float):
    # Don't do this - bypasses automatic query optimization
    var custom_entities = ECS.world.query.with_all([C_Position]).execute()
    # Process custom_entities...
```

### 3. Optimize Query Specificity

More specific queries run faster:

```gdscript
# ✅ Fast - Use enabled filter for active entities only
class_name PlayerInputSystem extends System
func query():
    return q.with_all([C_Input, C_Movement]).enabled(true)
    # Super fast enabled filtering + component matching

# ✅ Fast - Specific component query
class_name ProjectileSystem extends System
func query():
    return q.with_all([C_Projectile, C_Velocity])
    # Only matches projectiles - very specific
```

```gdscript
# ❌ Slow - Overly broad query
class_name UniversalSystem extends System
func query():
    return q.with_all([C_Position])
    # Matches almost everything in the game!

func process(entities: Array[Entity], components: Array, delta: float):
    # Now we need expensive type checking in a loop
    for entity in entities:
        if entity.has_component(C_Player):
            # Handle player...
        elif entity.has_component(C_Enemy):
            # Handle enemy...
        # This defeats the purpose of ECS!
```

### 4. Smart Use of with_any Queries

`with_any` queries are **much faster than before** but still slower than `with_all`. Use strategically:

```gdscript
# ✅ Good - with_any for legitimate OR scenarios
class_name DamageSystem extends System
func query():
    return q.with_any([C_Player, C_Enemy, C_NPC]).with_all([C_Health])
    # When you truly need "any of these types with health"

# ✅ Better - Split when entities have different behavior
class_name PlayerMovementSystem extends System
func query(): return q.with_all([C_Player, C_Movement])

class_name EnemyMovementSystem extends System
func query(): return q.with_all([C_Enemy, C_Movement])
# Split systems = simpler logic + better performance
```

### 5. Avoid Group Queries for Performance-Critical Code

Group queries are now the slowest option. Use component-based queries instead:

```gdscript
# ❌ Slow - Group-based query (~16ms for 10K entities)
class_name PlayerSystem extends System
func query():
    return q.with_group("player")

# ✅ Fast - Component-based query (~0.6ms for 10K entities)
class_name PlayerSystem extends System
func query():
    return q.with_all([C_Player])
```

## 🧱 Component Design for Performance

### Keep Components Lightweight

Smaller components = faster memory access:

```gdscript
# ✅ Good - Lightweight components
class_name C_Position extends Component
@export var position: Vector2

class_name C_Velocity extends Component
@export var velocity: Vector2

class_name C_Health extends Component
@export var current: float
@export var maximum: float
```

```gdscript
# ❌ Heavy - Bloated component
class_name MegaComponent extends Component
@export var position: Vector2
@export var velocity: Vector2
@export var health: float
@export var mana: float
@export var inventory: Array[Item] = []
@export var abilities: Array[Ability] = []
@export var dialogue_history: Array[String] = []
# Too much data in one place!
```

### Minimize Component Additions/Removals

Adding and removing components requires index updates. Batch component operations when possible:

```gdscript
# ✅ Good - Batch component operations
func setup_new_enemy(entity: Entity):
    # Add multiple components in one batch
    entity.add_components([
        C_Health.new(),
        C_Position.new(),
        C_Velocity.new(),
        C_Enemy.new()
    ])

# ✅ Good - Single component change when needed
func apply_damage(entity: Entity, damage: float):
    var health = entity.get_component(C_Health)
    health.current = clamp(health.current - damage, 0, health.maximum)

    if health.current <= 0:
        entity.add_component(C_Dead.new())  # Single component addition
```

### Choose Between Boolean Properties vs Components Based on Usage

The choice between boolean properties and separate components depends on how frequently states change and how many entities need them.

#### Use Boolean Properties for Frequently-Changing States

When states change often, boolean properties avoid expensive index updates:

```gdscript
# ✅ Good for frequently-changing states (buffs, status effects, etc.)
class_name C_EntityState extends Component
@export var is_stunned: bool = false
@export var is_invisible: bool = false
@export var is_invulnerable: bool = false

class_name MovementSystem extends System
func query():
    return q.with_all([C_Position, C_Velocity, C_EntityState])
    # All entities that might need states must have this component

func process(entity: Entity, delta: float):
    var state = entity.get_component(C_EntityState)
    if state.is_stunned:
        return  # Just a property check - no index updates

    # Process movement...
```

**Tradeoffs:**

- ✅ Fast state changes (no index rebuilds)
- ✅ Simple property checks in systems
- ❌ All entities need the state component (memory overhead)
- ❌ Less precise queries (can't easily find "only stunned entities")

#### Use Separate Components for Rare or Permanent States

When states are long-lasting or infrequent, separate components provide precise queries:

```gdscript
# ✅ Good for rare/permanent states (player vs enemy, permanent abilities)
class_name MovementSystem extends System
func query():
    return q.with_all([C_Position, C_Velocity]).with_none([C_Paralyzed])
    # Precise query - only entities that can move

# Separate systems can target specific states precisely
class_name ParalyzedSystem extends System
func query():
    return q.with_all([C_Paralyzed])  # Only paralyzed entities
```

**Tradeoffs:**

- ✅ Memory efficient (only entities with states have components)
- ✅ Precise queries for specific states
- ❌ State changes trigger expensive index updates
- ❌ Complex queries with multiple exclusions

#### Guidelines:

- **High-frequency changes** (every few frames): Use boolean properties
- **Low-frequency changes** (minutes apart): Use separate components
- **Related states** (buffs/debuffs): Group into property components
- **Distinct entity types** (player/enemy): Use separate components

## ⚙️ System Performance Patterns

### Early Exit Strategies

Return early when no processing is needed:

```gdscript
class_name HealthRegenerationSystem extends System

func process(entities: Array[Entity], components: Array, delta: float):
    for entity in entities:
        var health = entity.get_component(C_Health)

        # Early exits for common cases
        if health.current >= health.maximum:
            continue  # Already at full health

        if health.regeneration_rate <= 0:
            continue  # No regeneration configured

        # Only do expensive work when needed
        health.current = min(health.current + health.regeneration_rate * delta, health.maximum)
```

### Batch Entity Operations

Group entity operations together:

```gdscript
# ✅ Good - Batch creation
func spawn_enemy_wave():
    var enemies: Array[Entity] = []

    # Create all entities using entity pooling
    for i in range(50):
        var enemy = ECS.world.create_entity()  # Uses entity pool for performance
        setup_enemy_components(enemy)
        enemies.append(enemy)

    # Add all to world at once
    ECS.world.add_entities(enemies)

# ✅ Good - Individual removal (batch removal not available)
func cleanup_dead_entities():
    var dead_entities = ECS.world.query.with_all([C_Dead]).execute()
    for entity in dead_entities:
        ECS.world.remove_entity(entity)  # Remove individually
```

## 📊 Performance Targets

### Frame Rate Targets

Aim for these processing times per frame:

- **60 FPS target**: ECS processing < 16ms per frame
- **30 FPS target**: ECS processing < 33ms per frame
- **Mobile target**: ECS processing < 8ms per frame

### Entity Scale Guidelines

GECS handles these entity counts well with proper optimization:

- **Small games**: 100-500 entities
- **Medium games**: 500-2000 entities
- **Large games**: 2000-10000 entities
- **Massive games**: 10000+ entities (requires advanced optimization)

## 🎯 Next Steps

1. **Profile your current game** to establish baseline performance
2. **Apply query optimizations** from this guide
3. **Redesign heavy components** into lighter, focused ones
4. **Implement system improvements** like early exits and batching
5. **Consider advanced techniques** like pooling and spatial partitioning for demanding scenarios

## 🔍 Additional Performance Features

### Entity Pooling

GECS includes built-in entity pooling for optimal performance:

```gdscript
# Use the entity pool for frequent entity creation/destruction
var new_entity = ECS.world.create_entity()  # Gets from pool when available
```

### Query Cache Statistics

Monitor query performance with built-in cache tracking:

```gdscript
# Get detailed cache performance data
var stats = ECS.world.get_cache_stats()
print("Cache hit rate: ", stats.get("hits", 0) / (stats.get("hits", 0) + stats.get("misses", 1)))
```

**Need more help?** Check the [Troubleshooting Guide](TROUBLESHOOTING.md) for specific performance issues.

---

_"Fast ECS code isn't about clever tricks - it's about designing systems that naturally align with how the framework works best."_
