2024-05-05 17:01:56 +10:00
|
|
|
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
|
2024-05-09 20:52:01 +10:00
|
|
|
let windowTitle: String
|
2024-05-05 17:01:56 +10:00
|
|
|
let bundle: Bundle
|
|
|
|
|
2024-05-09 20:52:01 +10:00
|
|
|
public init(resizable: Bool, vSync: VSyncMode, windowWidth: Int32, windowHeight: Int32, title: String, bundle: Bundle)
|
2024-05-05 17:01:56 +10:00
|
|
|
{
|
|
|
|
self.resizable = resizable
|
|
|
|
self.vSync = vSync
|
|
|
|
self.windowWidth = windowWidth
|
|
|
|
self.windowHeight = windowHeight
|
2024-05-09 20:52:01 +10:00
|
|
|
self.windowTitle = title
|
2024-05-05 17:01:56 +10:00
|
|
|
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 }
|
2024-05-09 20:52:01 +10:00
|
|
|
window = SDL_CreateWindow(configuration.windowTitle, winPos, winPos, winWidth, winHeight, winFlags)
|
2024-05-05 17:01:56 +10:00
|
|
|
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") }
|
|
|
|
|
2024-05-09 20:52:01 +10:00
|
|
|
for idx in 0...SDL_NumJoysticks()
|
2024-05-05 17:01:56 +10:00
|
|
|
{
|
2024-05-09 20:52:01 +10:00
|
|
|
if SDL_IsGameController(idx) != SDL_TRUE { continue }
|
2024-05-09 22:00:38 +10:00
|
|
|
Input.instance.padConnectedEvent(deviceIndex: idx)
|
2024-05-05 17:01:56 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2024-05-09 20:52:01 +10:00
|
|
|
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)
|
2024-05-05 17:01:56 +10:00
|
|
|
case SDL_CONTROLLERDEVICEADDED:
|
2024-05-09 20:52:01 +10:00
|
|
|
if SDL_IsGameController(event.cdevice.which) != SDL_TRUE { break }
|
2024-05-09 22:00:38 +10:00
|
|
|
Input.instance.padConnectedEvent(deviceIndex: event.cdevice.which)
|
2024-05-05 17:01:56 +10:00
|
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
2024-05-09 22:00:38 +10:00
|
|
|
Input.instance.padRemovedEvent(id: event.cdevice.which)
|
2024-05-09 20:52:01 +10:00
|
|
|
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)
|
2024-05-05 17:01:56 +10:00
|
|
|
default: break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let time = SDL_GetPerformanceCounter();
|
2024-05-09 20:52:01 +10:00
|
|
|
let deltaTime = Float(Double(time &- prevTime) * timeMul)
|
2024-05-05 17:01:56 +10:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|