initial commit

This commit is contained in:
robojerk 2025-12-15 16:03:37 -08:00
commit 6d27a8ed3a
17 changed files with 4440 additions and 0 deletions

View file

@ -0,0 +1,334 @@
/// EngineCore - Platform-agnostic game engine core
/// NO PLATFORM-SPECIFIC IMPORTS ALLOWED IN THIS MODULE
import Foundation
import RendererAPI
import PhysicsEngine
import AssetLoader
// MARK: - Engine Configuration
/// Configuration for the game engine
public struct EngineConfig: Sendable {
public var targetFrameRate: Int
public var fixedTimeStep: Double
public var maxFrameSkip: Int
public init(targetFrameRate: Int = 60, fixedTimeStep: Double = 1.0/60.0, maxFrameSkip: Int = 5) {
self.targetFrameRate = targetFrameRate
self.fixedTimeStep = fixedTimeStep
self.maxFrameSkip = maxFrameSkip
}
}
// MARK: - Game Engine
/// Main game engine class - coordinates all subsystems
public actor GameEngine {
// Platform abstraction layers (injected by platform-specific code)
private let renderer: any Renderer
private let windowManager: any WindowManager
private let inputHandler: any InputHandler
private let audioEngine: any AudioEngine
// Core systems
private let physicsEngine: PhysicsWorld
private let assetLoader: AssetManager
// Engine state
private var isRunning: Bool = false
private var config: EngineConfig
// Scene management
private var currentScene: Scene
// Timing
private var lastFrameTime: Double = 0
private var accumulator: Double = 0
private var frameCount: UInt64 = 0
// MARK: - Initialization
/// Initialize the engine with platform-specific abstractions
public init(
renderer: any Renderer,
windowManager: any WindowManager,
inputHandler: any InputHandler,
audioEngine: any AudioEngine,
config: EngineConfig = EngineConfig()
) {
self.renderer = renderer
self.windowManager = windowManager
self.inputHandler = inputHandler
self.audioEngine = audioEngine
self.config = config
// Initialize core systems (platform-agnostic)
self.physicsEngine = PhysicsWorld()
self.assetLoader = AssetManager()
// Create default scene
self.currentScene = Scene(
entities: [],
camera: Camera(),
lights: []
)
print("🎮 SportsBallEngine initialized")
}
// MARK: - Engine Lifecycle
/// Start the engine and run the main loop
public func start() async throws {
print("🚀 Starting engine...")
// Initialize all systems
try await initializeSystems()
// Load initial scene/assets
try await loadInitialScene()
// Start the main loop
isRunning = true
lastFrameTime = getCurrentTime()
print("✅ Engine started successfully")
// Run the game loop
await runMainLoop()
}
/// Stop the engine and cleanup
public func stop() async {
print("🛑 Stopping engine...")
isRunning = false
await shutdownSystems()
print("✅ Engine stopped")
}
// MARK: - System Initialization
private func initializeSystems() async throws {
print(" → Initializing renderer...")
let windowSize = windowManager.getSize()
let rendererConfig = RendererConfig(
windowHandle: windowManager.getNativeHandle(),
width: windowSize.width,
height: windowSize.height,
vsyncEnabled: true,
msaaSamples: 4
)
try await renderer.initialize(config: rendererConfig)
print(" ✓ Renderer: \(renderer.info.apiName) \(renderer.info.apiVersion)")
print(" ✓ Device: \(renderer.info.deviceName)")
print(" → Initializing input system...")
try await inputHandler.initialize()
print(" → Initializing audio system...")
try await audioEngine.initialize(config: AudioConfig())
print(" → Initializing physics engine...")
await physicsEngine.initialize()
print(" → Initializing asset loader...")
await assetLoader.initialize()
}
private func shutdownSystems() async {
await renderer.shutdown()
await inputHandler.shutdown()
await audioEngine.shutdown()
await physicsEngine.shutdown()
await assetLoader.shutdown()
}
// MARK: - Main Game Loop (Carmack-style fixed timestep)
private func runMainLoop() async {
while isRunning {
let currentTime = getCurrentTime()
let frameTime = currentTime - lastFrameTime
lastFrameTime = currentTime
// Cap frame time to prevent spiral of death
let clampedFrameTime = min(frameTime, config.fixedTimeStep * Double(config.maxFrameSkip))
accumulator += clampedFrameTime
// Process window events
let windowEvents = windowManager.processEvents()
for event in windowEvents {
handleWindowEvent(event)
}
// Check if window should close
if windowManager.shouldClose() {
isRunning = false
break
}
// Process input events
let inputEvents = inputHandler.pollEvents()
handleInputEvents(inputEvents)
// Fixed timestep update loop (deterministic physics/gameplay)
while accumulator >= config.fixedTimeStep {
try? await fixedUpdate(deltaTime: Float(config.fixedTimeStep))
accumulator -= config.fixedTimeStep
}
// Variable timestep render
let alpha = Float(accumulator / config.fixedTimeStep)
try? await render(interpolationAlpha: alpha)
frameCount += 1
}
}
// MARK: - Update & Render
/// Fixed timestep update for physics and gameplay logic
private func fixedUpdate(deltaTime: Float) async throws {
// Update physics simulation
await physicsEngine.step(deltaTime: deltaTime)
// Update audio engine
try audioEngine.update(deltaTime: deltaTime)
// Update game logic (this would call game-specific code)
updateGameLogic(deltaTime: deltaTime)
// Sync physics state to scene entities
syncPhysicsToScene()
}
/// Render the current frame
private func render(interpolationAlpha: Float) async throws {
try renderer.beginFrame()
// Interpolate entity positions for smooth rendering
let interpolatedScene = interpolateScene(currentScene, alpha: interpolationAlpha)
// Draw the scene
try renderer.draw(scene: interpolatedScene)
try renderer.endFrame()
try windowManager.swapBuffers()
}
// MARK: - Scene Management
private func loadInitialScene() async throws {
print(" → Loading initial scene...")
// Create a simple test scene (stadium environment)
let stadium = try await createStadiumScene()
currentScene = stadium
print(" ✓ Scene loaded: \(currentScene.entities.count) entities")
}
private func createStadiumScene() async throws -> Scene {
// This would load actual assets, for now create a placeholder
let camera = Camera(
position: SIMD3<Float>(0, 10, 20),
rotation: SIMD4<Float>(0, 0, 0, 1),
fieldOfView: 60.0
)
let sunLight = Light(
type: .directional,
direction: SIMD3<Float>(-0.3, -1, -0.3),
color: SIMD3<Float>(1, 0.95, 0.8),
intensity: 1.0
)
return Scene(
entities: [],
camera: camera,
lights: [sunLight]
)
}
// MARK: - Event Handling
private func handleWindowEvent(_ event: WindowEvent) {
switch event {
case .close:
isRunning = false
case .resize(let width, let height):
print(" ↔ Window resized: \(width)x\(height)")
// Notify renderer of resize
case .focus(let focused):
print(" 👁 Window focus changed: \(focused)")
case .minimize:
print(" ⬇ Window minimized")
case .maximize:
print(" ⬆ Window maximized")
case .restore:
print(" ↕ Window restored")
}
}
private func handleInputEvents(_ events: [InputEvent]) {
for event in events {
switch event {
case .keyEvent(let key, let action):
if key == .escape && action == .press {
isRunning = false
}
default:
break
}
}
}
// MARK: - Game Logic
private func updateGameLogic(deltaTime: Float) {
// This is where sports game logic would go:
// - Player AI
// - Ball physics
// - Animation state machines
// - Collision detection
// - Score tracking
// etc.
}
private func syncPhysicsToScene() {
// Sync physics simulation results back to scene entities
// This would update entity transforms based on physics bodies
}
private func interpolateScene(_ scene: Scene, alpha: Float) -> Scene {
// Interpolate between previous and current physics state for smooth rendering
// For now, just return the scene as-is
return scene
}
// MARK: - Public API
public func loadScene(_ scene: Scene) {
currentScene = scene
}
public func getFrameCount() -> UInt64 {
return frameCount
}
public func getFPS() -> Double {
// Calculate FPS based on frame timing
return 60.0 // Placeholder
}
// MARK: - Utilities
private func getCurrentTime() -> Double {
return Double(DispatchTime.now().uptimeNanoseconds) / 1_000_000_000.0
}
}