import Foundation import SDL public protocol ApplicationImplementation { func create(render: inout Renderer) func quit() func loadContent(content: inout ContentManager) throws func resize(width: Int, height: Int) func update(deltaTime: Float) func draw(render: inout Renderer, deltaTime: Float) } extension ApplicationImplementation { public func quit() {} } public struct ApplicationConfiguration { let resizable: Bool let vSync: VSyncMode let windowWidth: Int32 let windowHeight: Int32 let windowTitle: String let bundle: Bundle public init(resizable: Bool, vSync: VSyncMode, windowWidth: Int32, windowHeight: Int32, title: String, bundle: Bundle) { self.resizable = resizable self.vSync = vSync self.windowWidth = windowWidth self.windowHeight = windowHeight self.windowTitle = title self.bundle = bundle } } public class Application { private let implementation: ApplicationImplementation private let configuration: ApplicationConfiguration private var window: OpaquePointer? private var render: Renderer private var content: ContentManager typealias ResizeEvent = (width: Int32, height: Int32) private var resize: ResizeEvent? = nil public init(application: ApplicationImplementation, config: ApplicationConfiguration) { self.implementation = application self.configuration = config self.render = OpenGL(version: .init(major: 1, minor: 5)) self.content = ContentManager(&self.render, bundle: configuration.bundle) } public func run() { guard SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) == 0 else { fatalError("SDL_Init: \(String(cString: SDL_GetError()))") } let winPos = Int32(SDL_WINDOWPOS_UNDEFINED_MASK) let winWidth: Int32 = configuration.windowWidth, winHeight: Int32 = configuration.windowHeight var winFlags = SDL_WINDOW_RESIZABLE.rawValue | SDL_WINDOW_ALLOW_HIGHDPI.rawValue if render is OpenGL { winFlags |= SDL_WINDOW_OPENGL.rawValue } window = SDL_CreateWindow(configuration.windowTitle, winPos, winPos, winWidth, winHeight, winFlags) guard window != nil else { fatalError("SDL_CreateWindow: \(String(cString: SDL_GetError()))") } do { try render.create(sdlWindow: window!) try render.setVsync(mode: configuration.vSync) } catch RendererError.sdlError(let message) { fatalError("idk error \(message)") } catch { fatalError("piss") } for idx in 0...SDL_NumJoysticks() { if SDL_IsGameController(idx) != SDL_TRUE { continue } Input.instance.padConnectedEvent(index: idx) } do { implementation.create(render: &render) content.setLoader(extension: "obj", loader: ObjLoader()) content.setLoader(extension: "g3db", loader: G3DbLoader()) content.setLoader(extensions: ["png", "jpg"], loader: NSImageLoader()) try implementation.loadContent(content: &content) } catch { fatalError("loadContent()") } let timeMul = 1 / Double(SDL_GetPerformanceFrequency()); var prevTime = SDL_GetPerformanceCounter(); resize = displaySize mainLoop: while true { Input.instance.newTick() var event = SDL_Event() while SDL_PollEvent(&event) > 0 { switch SDL_EventType(event.type) { case SDL_QUIT: break mainLoop case SDL_WINDOWEVENT: switch SDL_WindowEventID(UInt32(event.window.event)) { case SDL_WINDOWEVENT_RESIZED: resize = displaySize default: break } case SDL_KEYDOWN: Input.instance.pressEvent(scan: event.key.keysym.scancode, repeat: event.key.repeat != 0) case SDL_KEYUP: Input.instance.releaseEvent(scan: event.key.keysym.scancode) case SDL_CONTROLLERDEVICEADDED: if SDL_IsGameController(event.cdevice.which) != SDL_TRUE { break } Input.instance.padConnectedEvent(index: event.cdevice.which) case SDL_CONTROLLERDEVICEREMOVED: Input.instance.padRemovedEvent(index: event.cdevice.which) case SDL_CONTROLLERBUTTONDOWN: Input.instance.padButtonPressEvent(id: event.cbutton.which, btn: event.cbutton.button) case SDL_CONTROLLERBUTTONUP: Input.instance.padButtonReleaseEvent(id: event.cbutton.which, btn: event.cbutton.button) case SDL_CONTROLLERAXISMOTION: Input.instance.padAxisEvent(id: event.caxis.which, axis: event.caxis.axis, value: event.caxis.value) default: break } } let time = SDL_GetPerformanceCounter(); let deltaTime = Float(Double(time &- prevTime) * timeMul) prevTime = time implementation.update(deltaTime: min(deltaTime, 1.0 / 15.0)) repaint(deltaTime) } implementation.quit() content.releaseAll() render.delete() SDL_DestroyWindow(window) SDL_Quit() } private func repaint(_ deltaTime: Float) { if resize != nil { render.resize(width: resize!.width, height: resize!.height) implementation.resize(width: Int(resize!.width), height: Int(resize!.height)) resize = nil } render.newFrame() implementation.draw(render: &render, deltaTime: deltaTime) SDL_GL_SwapWindow(window) } private var displaySize: ResizeEvent { var width: Int32 = 0, height: Int32 = 0 SDL_GL_GetDrawableSize(window, &width, &height) return (width, height) } }