SportsBallEngine/ARCHITECTURE.md
2025-12-15 16:03:37 -08:00

13 KiB

🏗️ SportsBallEngine Architecture

This document provides a detailed overview of the engine's architecture, following industry-standard design patterns for cross-platform game development.

Core Principles

1. Strict Layer Separation

The engine is divided into two distinct categories:

Platform-Agnostic Layer (Engine Core)

  • Pure Swift code with ZERO platform-specific imports
  • Contains all game logic, physics, and asset management
  • Can be tested and developed independently of any platform
  • Modules: EngineCore, PhysicsEngine, AssetLoader

Platform Abstraction Layer (PAL)

  • Thin wrappers around OS-specific APIs
  • Implements protocols defined by the core layer
  • Only place where platform-specific code exists
  • Modules: PlatformLinux, PlatformWin32, VulkanRenderer, DX12Renderer

2. Protocol-Oriented Design

All platform-dependent functionality is accessed through Swift protocols:

// RendererAPI module (platform-agnostic)
public protocol Renderer: Sendable {
    func initialize(config: RendererConfig) async throws
    func beginFrame() throws
    func draw(scene: Scene) throws
    func endFrame() throws
    func shutdown() async
    // ... more methods
}

// VulkanRenderer module (platform-specific)
public final class VulkanRenderer: Renderer {
    // Concrete Vulkan implementation
}

// DX12Renderer module (platform-specific)
public final class DX12Renderer: Renderer {
    // Concrete DirectX 12 implementation
}

3. Dependency Injection

Platform implementations are created and injected at startup:

// main.swift
let renderer = PlatformFactory.createRenderer()      // Creates Vulkan or DX12
let windowManager = PlatformFactory.createWindowManager()
let inputHandler = PlatformFactory.createInputHandler()
let audioEngine = PlatformFactory.createAudioEngine()

// Inject into engine core
let engine = GameEngine(
    renderer: renderer,
    windowManager: windowManager,
    inputHandler: inputHandler,
    audioEngine: audioEngine
)

Module Dependency Graph

┌─────────────────────────────────────────────────────────────┐
│                         main.swift                          │
│                  (Platform Detection)                       │
└────────────────┬────────────────────────────────────────────┘
                 │
                 │ Creates & Injects
                 │
                 ▼
┌─────────────────────────────────────────────────────────────┐
│                       EngineCore                            │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐           │
│  │ Main Loop  │  │   Scene    │  │  Systems   │           │
│  │ (Fixed Δt) │  │ Management │  │Coordinator │           │
│  └────────────┘  └────────────┘  └────────────┘           │
│                                                             │
│  Depends on protocols only:                                │
│    - Renderer (draw calls)                                 │
│    - WindowManager (events)                                │
│    - InputHandler (keyboard/mouse)                         │
│    - AudioEngine (sound)                                   │
└────────┬───────────────────┬──────────────────────────────┘
         │                   │
         │                   │
    ┌────▼────┐         ┌────▼────┐
    │ Physics │         │  Asset  │
    │ Engine  │         │ Loader  │
    └─────────┘         └─────────┘


┌─────────────────────────────────────────────────────────────┐
│                    RendererAPI (Protocols)                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ Renderer │  │  Window  │  │  Input   │  │  Audio   │  │
│  │ Protocol │  │ Protocol │  │ Protocol │  │ Protocol │  │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘  │
│                                                             │
│  Also defines: Scene, Camera, Entity, Transform, etc.     │
└────┬─────────────┬─────────────┬─────────────┬────────────┘
     │             │             │             │
     │             │             │             │
     │             │             │             │
┌────▼────┐   ┌────▼────┐   ┌────▼────┐   ┌────▼────┐
│ Vulkan  │   │  DX12   │   │ Platform│   │ Platform│
│Renderer │   │Renderer │   │  Linux  │   │  Win32  │
└─────────┘   └─────────┘   └─────────┘   └─────────┘
     │             │             │             │
     │             │             │             │
┌────▼────────────▼─────────────▼─────────────▼────┐
│           Operating System APIs                   │
│  Vulkan | DirectX 12 | X11 | Wayland | Win32     │
└───────────────────────────────────────────────────┘

Data Flow

Frame Rendering Flow

1. main.swift
   └─> GameEngine.runMainLoop()

2. EngineCore
   ├─> windowManager.processEvents()        [Platform layer]
   ├─> inputHandler.pollEvents()            [Platform layer]
   │
   ├─> fixedUpdate(deltaTime) [Fixed timestep]
   │   ├─> physicsEngine.step()             [Core layer]
   │   ├─> audioEngine.update()             [Platform layer]
   │   └─> updateGameLogic()                [Core layer]
   │
   └─> render(interpolationAlpha)
       ├─> renderer.beginFrame()            [Platform layer]
       ├─> renderer.draw(scene)             [Platform layer]
       └─> renderer.endFrame()              [Platform layer]

3. Renderer (Vulkan or DX12)
   ├─> Acquire swapchain image
   ├─> Record command buffer
   │   ├─> Bind pipeline
   │   ├─> Set viewport/scissor
   │   └─> For each entity:
   │       ├─> Bind vertex/index buffers
   │       └─> Draw indexed
   ├─> Submit command buffer
   └─> Present to window

Asset Loading Flow

1. EngineCore
   └─> assetLoader.loadMesh("player.fbx")

2. AssetLoader
   ├─> Read file from disk
   ├─> Parse FBX format
   ├─> Extract vertices, indices, bones
   └─> Return MeshData (CPU-side)

3. EngineCore
   └─> renderer.loadMesh(vertices, indices)

4. Renderer (Vulkan or DX12)
   ├─> Allocate GPU buffer
   ├─> Upload data to GPU
   └─> Return MeshHandle (GPU resource)

5. EngineCore
   └─> Store MeshHandle for rendering

Key Abstractions

Renderer Protocol

Handles all graphics API calls:

  • Frame management (begin/end)
  • Scene rendering
  • Resource loading (meshes, textures)
  • Material creation

Implementations: VulkanRenderer, DX12Renderer

WindowManager Protocol

Handles OS window management:

  • Window creation/destruction
  • Event processing (resize, close, etc.)
  • Native handle retrieval (for surface creation)
  • Fullscreen toggling

Implementations: LinuxWindowManager, Win32WindowManager

InputHandler Protocol

Handles user input:

  • Keyboard state
  • Mouse state and position
  • Gamepad/controller support
  • Input events

Implementations: LinuxInputHandler, Win32InputHandler

AudioEngine Protocol

Handles 3D audio:

  • Audio loading and playback
  • 3D spatial audio
  • Listener position (camera)
  • Volume control

Implementations: LinuxAudioEngine, Win32AudioEngine

Sports Game Optimizations

Physics System

The PhysicsEngine is optimized for sports scenarios:

// Special tracking for sports entities
private var ballBodies: Set<UUID> = []      // Balls/pucks
private var playerBodies: Set<UUID> = []    // Players

// Custom physics for balls
if ballBodies.contains(id) {
    // Apply spin, air drag, magnus effect
    let drag = -body.velocity * dragCoefficient
    body.acceleration += drag / body.mass
}

Animation System (Future)

Skeletal animation with state machines:

  • Running → Shooting transition
  • Tackling animations
  • Celebration sequences
  • Injury reactions

Stadium Rendering (Future)

Large environment optimizations:

  • LOD (Level of Detail) for distant objects
  • Occlusion culling
  • Crowd rendering (instancing)
  • Particle systems (grass, dust)

Thread Safety

The engine uses Swift 6 concurrency features:

  • actor for thread-safe state management
  • Sendable protocols for cross-thread data
  • async/await for asynchronous operations
  • @unchecked Sendable for platform handles
// EngineCore is an actor - all access is serialized
public actor GameEngine {
    private let physicsEngine: PhysicsWorld
    private var currentScene: Scene
    // ...
}

Build System

Uses Swift Package Manager with modular targets:

// Package.swift structure
targets: [
    // Executable
    .executableTarget(name: "SportsBallEngine", dependencies: [...]),
    
    // Core modules (platform-agnostic)
    .target(name: "EngineCore", dependencies: ["RendererAPI", ...]),
    .target(name: "RendererAPI", dependencies: []),
    .target(name: "PhysicsEngine", dependencies: []),
    .target(name: "AssetLoader", dependencies: []),
    
    // Platform modules (platform-specific)
    .target(name: "VulkanRenderer", dependencies: ["RendererAPI"]),
    .target(name: "DX12Renderer", dependencies: ["RendererAPI"]),
    .target(name: "PlatformLinux", dependencies: ["RendererAPI", "VulkanRenderer"]),
    .target(name: "PlatformWin32", dependencies: ["RendererAPI", "DX12Renderer"]),
]

Testing Strategy

Unit Tests

  • Test core modules in isolation (no platform dependencies)
  • Mock platform implementations using protocols
  • Physics simulation verification
  • Asset parsing validation

Integration Tests

  • Test platform implementations on target OS
  • Renderer functionality tests
  • Window management tests
  • Input handling tests

Performance Tests

  • Frame time consistency
  • Physics simulation speed
  • Asset loading performance
  • Memory usage profiling

Extension Points

Adding a New Platform

  1. Create Sources/Platform{Name}/
  2. Implement all protocols:
    • WindowManager
    • InputHandler
    • AudioEngine
  3. Choose or implement renderer
  4. Update PlatformFactory in main.swift

Adding a New Renderer

  1. Create Sources/{API}Renderer/
  2. Implement Renderer protocol
  3. Handle resource loading (meshes, textures)
  4. Implement draw commands
  5. Add to PlatformFactory

Adding Game-Specific Features

All game logic stays in EngineCore or custom modules:

  • Player AI systems
  • Ball physics customization
  • Game rules and scoring
  • Network synchronization

Never add game logic to platform layers!

Performance Considerations

Fixed Timestep

Uses fixed timestep for deterministic physics:

// Variable framerate for rendering
// Fixed 60Hz for physics/gameplay
while accumulator >= fixedTimeStep {
    fixedUpdate(deltaTime: fixedTimeStep)
    accumulator -= fixedTimeStep
}

// Interpolate between physics states for smooth rendering
render(interpolationAlpha: accumulator / fixedTimeStep)

Memory Management

  • GPU resources managed through opaque handles
  • Asset caching with LRU eviction (future)
  • Pool allocators for frequent objects (future)
  • Reference counting for shared resources

Multithreading (Future)

  • Physics on separate thread
  • Async asset loading
  • GPU command recording parallelization
  • Job system for parallel tasks

This architecture ensures:

  • Clean separation of concerns
  • Easy platform porting
  • Testable core logic
  • Maintainable codebase
  • Performance optimization opportunities
  • Sports game-specific features