Skip to main content

Gaming: Game Loops, ECS Patterns, and State Synchronization

Architecture for real-time multiplayer games with low latency and consistency

TL;DR

Games run in tight loops (60+ FPS) processing input, updating state, rendering. Fixed timestep ensures physics determinism (100ms per frame). Variable timestep adapts to frame rate but requires interpolation. ECS (Entity-Component-System) decouples data (components) from logic (systems), enabling efficient batching. Multiplayer games sync state across clients with network prediction to hide latency (player shoots, server confirms). Object pooling pre-allocates bullets/effects to avoid GC pauses. Spatial partitioning culls invisible entities, reducing network traffic.

Learning Objectives

  • Understand game loop architecture and timestep choices
  • Implement entity-component-system pattern
  • Synchronize game state across network
  • Use network prediction and interpolation
  • Optimize with spatial partitioning and object pooling
  • Handle latency and input prediction

Motivating Scenario

You're building a multiplayer FPS. 100 players, each firing at 1000 rounds per second. Server receives bullets, must hit-detect, update 100 clients within 16ms (60 FPS). If you send each bullet event to all clients, network explodes (100,000 messages/sec). Solutions: client-side prediction (shoot locally, server validates), spatial partitioning (only sync entities near player), object pooling (reuse bullet objects, avoid allocations).

Core Concepts

Game systems balance correctness, latency, and bandwidth:

Game Loop: Input → Update → Render. Repeats at fixed rate (60 FPS = 16.67ms/frame).

Fixed Timestep: Each frame is exactly dt time (e.g., 16ms). Physics deterministic. But might skip/duplicate frames.

Variable Timestep: Adapt to frame rate. Smooth but requires interpolation, non-deterministic physics.

ECS: Entities (player, bullet). Components (position, velocity, health). Systems (physics, rendering).

Network Prediction: Client predicts local action (shoot, move), server validates asynchronously.

Interpolation: Smooth movement between server-sent positions using past frame data.

Spatial Partitioning: Divide world into grid. Only sync nearby entities (reduces bandwidth 100x).

Game loop with ECS and network synchronization

Key Concepts

Delta Time (dt): Time since last frame. Used to scale movement (position += velocity * dt).

Latency Compensation: Client prediction + server confirmation minimizes perceived lag.

Client Authority vs Server Authority: Client simulates locally (fast, can cheat), server validates (correct but slow).

State Snapshots: Server periodically sends full game state. Clients interpolate between snapshots.

Frame Interpolation: Render at frame time between server updates. Smooth 60 FPS from 20 Hz server updates.

Practical Example

import time
from typing import Dict, List
from dataclasses import dataclass, field

@dataclass
class Vector2:
x: float = 0.0
y: float = 0.0

def __add__(self, other):
return Vector2(self.x + other.x, self.y + other.y)

def __mul__(self, scalar):
return Vector2(self.x * scalar, self.y * scalar)

@dataclass
class Component:
pass

@dataclass
class Transform(Component):
position: Vector2 = field(default_factory=Vector2)
velocity: Vector2 = field(default_factory=Vector2)

@dataclass
class Health(Component):
hp: int = 100

@dataclass
class Entity:
id: int
components: Dict[type, Component] = field(default_factory=dict)

def add_component(self, component: Component):
self.components[type(component)] = component

def get_component(self, comp_type: type) -> Component:
return self.components.get(comp_type)

class PhysicsSystem:
@staticmethod
def update(entities: List[Entity], dt: float):
for entity in entities:
transform = entity.get_component(Transform)
if transform:
# position += velocity * dt
transform.position = transform.position + transform.velocity * dt

class RenderSystem:
@staticmethod
def render(entities: List[Entity]):
for entity in entities:
transform = entity.get_component(Transform)
if transform:
print(f"Entity {entity.id}: ({transform.position.x:.1f}, {transform.position.y:.1f})")

class GameLoop:
def __init__(self, target_fps: int = 60):
self.target_fps = target_fps
self.frame_time = 1.0 / target_fps # 16.67ms @ 60 FPS
self.entities = []
self.entity_id_counter = 0

def create_entity(self) -> Entity:
entity = Entity(self.entity_id_counter)
self.entity_id_counter += 1
self.entities.append(entity)
return entity

def run(self, iterations: int = 3):
for frame in range(iterations):
frame_start = time.time()

# Input (simulate player input)
for entity in self.entities:
transform = entity.get_component(Transform)
if transform and entity.id == 0: # Player 0 moves right
transform.velocity = Vector2(100, 0)

# Update (physics, animation, logic)
PhysicsSystem.update(self.entities, self.frame_time)

# Render
print(f"\nFrame {frame}:")
RenderSystem.render(self.entities)

# Maintain fixed timestep
elapsed = time.time() - frame_start
sleep_time = max(0, self.frame_time - elapsed)
time.sleep(sleep_time)

# Example
game = GameLoop(target_fps=60)

# Create entities
player = game.create_entity()
player.add_component(Transform(position=Vector2(0, 0), velocity=Vector2(0, 0)))
player.add_component(Health(hp=100))

enemy = game.create_entity()
enemy.add_component(Transform(position=Vector2(100, 50), velocity=Vector2(-50, 0)))
enemy.add_component(Health(hp=50))

print("Game Loop (60 FPS, 16.67ms per frame)")
game.run(iterations=5)

When to Use / When Not to Use

Use Gaming Patterns When:
  1. Real-time multiplayer game (60+ FPS)
  2. Low latency critical (< 100ms perceived)
  3. High entity count (1000+ objects)
  4. Need deterministic physics
  5. Network bandwidth constrained
  6. Client prediction needed for responsiveness
Avoid Complex Gaming Patterns When:
  1. Turn-based game (seconds per turn)
  2. Single-player, no network sync
  3. Low entity count (< 100)
  4. Server authority is acceptable
  5. Simplicity prioritized over latency

Patterns and Pitfalls

Patterns and Pitfalls

Create bullet objects every frame. GC pauses cause frame drops (100ms freeze). Object pooling: pre-allocate bullets, reuse them. No allocation in hot path.
Client predicts movement, server disagrees. Player sees themselves teleport. Reconciliation: correct mispredictions smoothly. Validate server state.
Divide world into grid. Only sync entities in adjacent cells. Reduce network traffic 100x. Only send relevant entities to clients.
Physics deterministic (same inputs → same result). Multiplayer consistency. Advance physics by fixed dt, skip/duplicate frames as needed.
Render at server update rate (20 Hz). Jerky movement. Interpolate positions between updates. Smooth 60 FPS from 20 Hz updates.

Design Review Checklist

  • Is the game loop fixed timestep or variable? Justified?
  • Is ECS pattern used for entity-component decoupling?
  • Are systems batching queries efficiently (cache locality)?
  • Is object pooling used for frequently created/destroyed objects?
  • Is network synchronization client-predicting with server confirmation?
  • Is interpolation implemented for smooth rendering?
  • Is spatial partitioning reducing network traffic?
  • Can the frame rate sustain target FPS under max load?
  • Is latency compensation (prediction + reconciliation) transparent to player?
  • Are there no allocations in hot path (update/render)?

Self-Check

  1. Fixed vs variable timestep? Fixed: deterministic physics, easier consistency. Variable: smooth FPS adaptation, needs interpolation.
  2. Why ECS? Decouples data (components) from logic (systems). Enables efficient batching and cache locality.
  3. How to hide 100ms latency? Client prediction (shoot locally), server validation async. By the time server responds, it's not noticeable.
info

One Takeaway: Games are optimization machines. Every ms and MB matters. Profile first, then optimize.

Next Steps

  • Game Engines: Unity, Unreal, Godot physics loops
  • Networking: Photon, Mirror, Fusion for multiplayer
  • Spatial Structures: Quadtrees, R-trees, grid-based partitioning
  • Frame Pacing: Adaptive V-sync, frame interpolation
  • Prediction: Dead reckoning, Kalman filtering

References

  • Nystrom, R. (2014). Game Programming Patterns. ↗️
  • West, M. (2013). "Latency Compensation Methods in Client/Server In-game Protocol Design and Optimization." ↗️
  • GDC Vault: ECS talks from major studios. ↗️