initial commit
This commit is contained in:
commit
6d27a8ed3a
17 changed files with 4440 additions and 0 deletions
334
Sources/EngineCore/Engine.swift
Normal file
334
Sources/EngineCore/Engine.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue