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:
actorfor thread-safe state managementSendableprotocols for cross-thread dataasync/awaitfor asynchronous operations@unchecked Sendablefor 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
- Create
Sources/Platform{Name}/ - Implement all protocols:
WindowManagerInputHandlerAudioEngine
- Choose or implement renderer
- Update
PlatformFactoryinmain.swift
Adding a New Renderer
- Create
Sources/{API}Renderer/ - Implement
Rendererprotocol - Handle resource loading (meshes, textures)
- Implement draw commands
- 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