From d914e0f8fb70c2e28551607cb5e07223cab805f8 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 25 Jul 2026 07:23:19 +0200 Subject: [PATCH] Remove per-call overhead from API access and text conversion Two hot-path optimizations for the device's Cortex-M7 (and debug simulator builds): - The ten sub-API pointers are cached once in Playdate.initialize(with:); wrapper accessors now return UnsafePointer and call sites read a single field through it, instead of unwrapping the optional API struct and copying a whole sub-API struct of function pointers on every call. Nested sub-APIs (sound classes, effects, video, tilemap, http/tcp) derive from the cached pointers with one field load. - String -> C conversions (withPlaydateCString, and a new withPlaydateUTF8 used by the text drawing/measuring APIs) use withUnsafeTemporaryAllocation instead of building a ContiguousArray, so logging and drawText in the update loop no longer heap-allocate per call. Verified within the Embedded Swift subset by the device cross-compile. Co-Authored-By: Claude Fable 5 --- Sources/PlayDate/Display.swift | 22 ++-- Sources/PlayDate/File.swift | 34 +++--- Sources/PlayDate/Graphics.swift | 84 +++++++------ Sources/PlayDate/GraphicsBitmap.swift | 44 +++---- Sources/PlayDate/GraphicsFont.swift | 28 ++--- Sources/PlayDate/GraphicsTileMap.swift | 22 ++-- Sources/PlayDate/GraphicsVideo.swift | 42 +++---- Sources/PlayDate/JSON.swift | 8 +- Sources/PlayDate/Lua.swift | 64 +++++----- Sources/PlayDate/Network.swift | 114 +++++++++--------- Sources/PlayDate/PlayDate.swift | 23 ++++ Sources/PlayDate/Scoreboards.swift | 16 +-- Sources/PlayDate/Sound.swift | 70 +++++------ Sources/PlayDate/SoundEffect.swift | 124 +++++++++---------- Sources/PlayDate/SoundSignal.swift | 86 ++++++------- Sources/PlayDate/SoundSource.swift | 110 ++++++++--------- Sources/PlayDate/SoundSynth.swift | 160 ++++++++++++------------- Sources/PlayDate/Sprite.swift | 136 ++++++++++----------- Sources/PlayDate/Support.swift | 32 ++++- Sources/PlayDate/System.swift | 106 ++++++++-------- 20 files changed, 682 insertions(+), 643 deletions(-) diff --git a/Sources/PlayDate/Display.swift b/Sources/PlayDate/Display.swift index 8d68dd6..3e31b4f 100644 --- a/Sources/PlayDate/Display.swift +++ b/Sources/PlayDate/Display.swift @@ -9,49 +9,49 @@ internal import CPlaydate public enum Display {} extension Display { - private static var api: playdate_display { Playdate.api.display.pointee } + private static var api: UnsafePointer { Playdate.displayAPI } /// The display width in pixels, taking the current scale into account. - public static var width: Int { Int(api.getWidth.unsafelyUnwrapped()) } + public static var width: Int { Int(api.pointee.getWidth.unsafelyUnwrapped()) } /// The display height in pixels, taking the current scale into account. - public static var height: Int { Int(api.getHeight.unsafelyUnwrapped()) } + public static var height: Int { Int(api.pointee.getHeight.unsafelyUnwrapped()) } /// Sets the nominal refresh rate in frames per second. Pass 0 to update /// as fast as possible (the update callback drives the pace). public static func setRefreshRate(_ rate: Float) { - api.setRefreshRate.unsafelyUnwrapped(rate) + api.pointee.setRefreshRate.unsafelyUnwrapped(rate) } /// The current nominal refresh rate. - public static var refreshRate: Float { api.getRefreshRate.unsafelyUnwrapped() } + public static var refreshRate: Float { api.pointee.getRefreshRate.unsafelyUnwrapped() } /// The measured average frames per second. - public static var fps: Float { api.getFPS.unsafelyUnwrapped() } + public static var fps: Float { api.pointee.getFPS.unsafelyUnwrapped() } /// Draws the frame white-on-black when `true`. public static func setInverted(_ inverted: Bool) { - api.setInverted.unsafelyUnwrapped(inverted ? 1 : 0) + api.pointee.setInverted.unsafelyUnwrapped(inverted ? 1 : 0) } /// Sets the display scale factor: 1, 2, 4, or 8. public static func setScale(_ scale: UInt32) { - api.setScale.unsafelyUnwrapped(scale) + api.pointee.setScale.unsafelyUnwrapped(scale) } /// Adds a mosaic effect. Valid values for each axis are 0...3. public static func setMosaic(x: UInt32, y: UInt32) { - api.setMosaic.unsafelyUnwrapped(x, y) + api.pointee.setMosaic.unsafelyUnwrapped(x, y) } /// Flips the display on the given axes. public static func setFlipped(x: Bool, y: Bool) { - api.setFlipped.unsafelyUnwrapped(x ? 1 : 0, y ? 1 : 0) + api.pointee.setFlipped.unsafelyUnwrapped(x ? 1 : 0, y ? 1 : 0) } /// Offsets the display by the given amount. Areas outside the frame /// buffer draw black. public static func setOffset(x: Int, y: Int) { - api.setOffset.unsafelyUnwrapped(Int32(x), Int32(y)) + api.pointee.setOffset.unsafelyUnwrapped(Int32(x), Int32(y)) } } diff --git a/Sources/PlayDate/File.swift b/Sources/PlayDate/File.swift index 0de36a5..bc478e2 100644 --- a/Sources/PlayDate/File.swift +++ b/Sources/PlayDate/File.swift @@ -8,11 +8,11 @@ internal import CPlaydate -private var fileAPI: playdate_file { Playdate.api.file.pointee } +private var fileAPI: UnsafePointer { Playdate.fileAPI } /// The most recent file system error as a thrown error. private func lastFileError() -> PlaydateError { - PlaydateError(cString: fileAPI.geterr.unsafelyUnwrapped()) + PlaydateError(cString: fileAPI.pointee.geterr.unsafelyUnwrapped()) } /// The file API: access to the game's Data directory and pdx contents. @@ -62,7 +62,7 @@ extension File { var callback = each return path.withPlaydateCString { cPath in withUnsafeMutablePointer(to: &callback) { callbackPointer in - fileAPI.listfiles.unsafelyUnwrapped(cPath, { cName, userdata in + fileAPI.pointee.listfiles.unsafelyUnwrapped(cPath, { cName, userdata in guard let cName, let userdata else { return } let each = userdata.assumingMemoryBound(to: ((String) -> Void).self).pointee each(String(playdateCString: cName)) @@ -76,7 +76,7 @@ extension File { /// Information about the file or directory at `path`. public static func stat(_ path: String) throws(PlaydateError) -> Stat { var stat = FileStat() - let result = path.withPlaydateCString { fileAPI.stat.unsafelyUnwrapped($0, &stat) } + let result = path.withPlaydateCString { fileAPI.pointee.stat.unsafelyUnwrapped($0, &stat) } if result != 0 { throw lastFileError() } return Stat( isDirectory: stat.isdir != 0, @@ -88,7 +88,7 @@ extension File { /// Creates a directory (and intermediate directories) in the Data directory. public static func mkdir(_ path: String) throws(PlaydateError) { - let result = path.withPlaydateCString { fileAPI.mkdir.unsafelyUnwrapped($0) } + let result = path.withPlaydateCString { fileAPI.pointee.mkdir.unsafelyUnwrapped($0) } if result != 0 { throw lastFileError() } } @@ -96,7 +96,7 @@ extension File { /// `recursive` to be deleted with their contents. public static func unlink(_ path: String, recursive: Bool = false) throws(PlaydateError) { let result = path.withPlaydateCString { - fileAPI.unlink.unsafelyUnwrapped($0, recursive ? 1 : 0) + fileAPI.pointee.unlink.unsafelyUnwrapped($0, recursive ? 1 : 0) } if result != 0 { throw lastFileError() } } @@ -106,7 +106,7 @@ extension File { public static func rename(from: String, to: String) throws(PlaydateError) { let result = from.withPlaydateCString { cFrom in to.withPlaydateCString { cTo in - fileAPI.rename.unsafelyUnwrapped(cFrom, cTo) + fileAPI.pointee.rename.unsafelyUnwrapped(cFrom, cTo) } } if result != 0 { throw lastFileError() } @@ -123,7 +123,7 @@ extension File { /// Opens the file at `path`. public init(path: String, mode: Options) throws(PlaydateError) { let pointer = path.withPlaydateCString { - fileAPI.open.unsafelyUnwrapped($0, mode.cValue) + fileAPI.pointee.open.unsafelyUnwrapped($0, mode.cValue) } guard let pointer else { throw lastFileError() } self.pointer = pointer @@ -131,7 +131,7 @@ extension File { deinit { if !isClosed { - _ = fileAPI.close.unsafelyUnwrapped(pointer) + _ = fileAPI.pointee.close.unsafelyUnwrapped(pointer) } } @@ -139,13 +139,13 @@ extension File { public func close() throws(PlaydateError) { guard !isClosed else { return } isClosed = true - if fileAPI.close.unsafelyUnwrapped(pointer) != 0 { throw lastFileError() } + if fileAPI.pointee.close.unsafelyUnwrapped(pointer) != 0 { throw lastFileError() } } /// Reads up to `buffer.count` bytes into `buffer`. Returns the number /// of bytes read; 0 indicates end of file. public func read(into buffer: UnsafeMutableRawBufferPointer) throws(PlaydateError) -> Int { - let result = fileAPI.read.unsafelyUnwrapped( + let result = fileAPI.pointee.read.unsafelyUnwrapped( pointer, buffer.baseAddress, UInt32(buffer.count)) if result < 0 { throw lastFileError() } return Int(result) @@ -155,7 +155,7 @@ extension File { public func read(length: Int) throws(PlaydateError) -> [UInt8] { var bytes = [UInt8](repeating: 0, count: length) let result = bytes.withUnsafeMutableBytes { buffer in - fileAPI.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) + fileAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) } if result < 0 { throw lastFileError() } bytes.removeLast(length - Int(result)) @@ -165,7 +165,7 @@ extension File { /// Writes the buffer to the file. Returns the number of bytes written. @discardableResult public func write(_ buffer: UnsafeRawBufferPointer) throws(PlaydateError) -> Int { - let result = fileAPI.write.unsafelyUnwrapped( + let result = fileAPI.pointee.write.unsafelyUnwrapped( pointer, buffer.baseAddress, UInt32(buffer.count)) if result < 0 { throw lastFileError() } return Int(result) @@ -175,7 +175,7 @@ extension File { @discardableResult public func write(_ bytes: [UInt8]) throws(PlaydateError) -> Int { let result = bytes.withUnsafeBytes { buffer in - fileAPI.write.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) + fileAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) } if result < 0 { throw lastFileError() } return Int(result) @@ -190,21 +190,21 @@ extension File { /// Flushes buffered writes to disk. Returns the bytes written. @discardableResult public func flush() throws(PlaydateError) -> Int { - let result = fileAPI.flush.unsafelyUnwrapped(pointer) + let result = fileAPI.pointee.flush.unsafelyUnwrapped(pointer) if result < 0 { throw lastFileError() } return Int(result) } /// The current read/write offset. public func tell() throws(PlaydateError) -> Int { - let result = fileAPI.tell.unsafelyUnwrapped(pointer) + let result = fileAPI.pointee.tell.unsafelyUnwrapped(pointer) if result < 0 { throw lastFileError() } return Int(result) } /// Moves the read/write offset to `offset` relative to `origin`. public func seek(to offset: Int, from origin: SeekOrigin = .start) throws(PlaydateError) { - if fileAPI.seek.unsafelyUnwrapped(pointer, Int32(offset), origin.rawValue) != 0 { + if fileAPI.pointee.seek.unsafelyUnwrapped(pointer, Int32(offset), origin.rawValue) != 0 { throw lastFileError() } } diff --git a/Sources/PlayDate/Graphics.swift b/Sources/PlayDate/Graphics.swift index 1b191fc..4d16ad5 100644 --- a/Sources/PlayDate/Graphics.swift +++ b/Sources/PlayDate/Graphics.swift @@ -10,7 +10,7 @@ internal import CPlaydate /// The graphics API: drawing, bitmaps, fonts, tilemaps, and video. public enum Graphics {} -var gfx: playdate_graphics { Playdate.api.graphics.pointee } +var gfx: UnsafePointer { Playdate.graphicsAPI } extension Graphics { // MARK: - Screen constants @@ -185,100 +185,100 @@ extension Graphics { /// Clears the entire display, filling it with `color`. public static func clear(color: Color = .white) { - color.withLCDColor { gfx.clear.unsafelyUnwrapped($0) } + color.withLCDColor { gfx.pointee.clear.unsafelyUnwrapped($0) } } /// Sets the background color shown when the display is offset or for /// clear pixels in drawn images. public static func setBackgroundColor(_ color: SolidColor) { - gfx.setBackgroundColor.unsafelyUnwrapped(color.cValue) + gfx.pointee.setBackgroundColor.unsafelyUnwrapped(color.cValue) } /// Sets the mode that determines how source pixels combine with the /// destination. Returns the previous mode. @discardableResult public static func setDrawMode(_ mode: DrawMode) -> DrawMode { - DrawMode(gfx.setDrawMode.unsafelyUnwrapped(mode.cValue)) + DrawMode(gfx.pointee.setDrawMode.unsafelyUnwrapped(mode.cValue)) } /// Offsets all subsequent drawing by (dx, dy). public static func setDrawOffset(dx: Int, dy: Int) { - gfx.setDrawOffset.unsafelyUnwrapped(Int32(dx), Int32(dy)) + gfx.pointee.setDrawOffset.unsafelyUnwrapped(Int32(dx), Int32(dy)) } /// Sets the clip rect in world coordinates (affected by the draw offset). public static func setClipRect(x: Int, y: Int, width: Int, height: Int) { - gfx.setClipRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height)) + gfx.pointee.setClipRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height)) } /// Sets the clip rect in screen coordinates (unaffected by the draw offset). public static func setScreenClipRect(x: Int, y: Int, width: Int, height: Int) { - gfx.setScreenClipRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height)) + gfx.pointee.setScreenClipRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height)) } public static func clearClipRect() { - gfx.clearClipRect.unsafelyUnwrapped() + gfx.pointee.clearClipRect.unsafelyUnwrapped() } public static func setLineCapStyle(_ style: LineCapStyle) { - gfx.setLineCapStyle.unsafelyUnwrapped(style.cValue) + gfx.pointee.setLineCapStyle.unsafelyUnwrapped(style.cValue) } /// Sets the stencil applied to subsequent drawing. If `tile` is `true` /// the stencil image is tiled, and its width must be a multiple of 32. /// Pass `nil` to clear the stencil. public static func setStencil(_ image: Bitmap?, tile: Bool = false) { - gfx.setStencilImage.unsafelyUnwrapped(image?.pointer, tile ? 1 : 0) + gfx.pointee.setStencilImage.unsafelyUnwrapped(image?.pointer, tile ? 1 : 0) } /// Pushes a new drawing context targeting `target`, or the display if /// `target` is `nil`. public static func pushContext(_ target: Bitmap? = nil) { - gfx.pushContext.unsafelyUnwrapped(target?.pointer) + gfx.pointee.pushContext.unsafelyUnwrapped(target?.pointer) } public static func popContext() { - gfx.popContext.unsafelyUnwrapped() + gfx.pointee.popContext.unsafelyUnwrapped() } // MARK: - Shapes public static func drawLine(x1: Int, y1: Int, x2: Int, y2: Int, width: Int, color: Color) { color.withLCDColor { - gfx.drawLine.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2), Int32(width), $0) + gfx.pointee.drawLine.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2), Int32(width), $0) } } public static func fillTriangle(x1: Int, y1: Int, x2: Int, y2: Int, x3: Int, y3: Int, color: Color) { color.withLCDColor { - gfx.fillTriangle.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2), + gfx.pointee.fillTriangle.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2), Int32(x3), Int32(y3), $0) } } public static func drawRect(x: Int, y: Int, width: Int, height: Int, color: Color) { color.withLCDColor { - gfx.drawRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0) + gfx.pointee.drawRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0) } } public static func fillRect(x: Int, y: Int, width: Int, height: Int, color: Color) { color.withLCDColor { - gfx.fillRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0) + gfx.pointee.fillRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0) } } public static func drawRoundRect(x: Int, y: Int, width: Int, height: Int, radius: Int, lineWidth: Int, color: Color) { color.withLCDColor { - gfx.drawRoundRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), + gfx.pointee.drawRoundRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), Int32(radius), Int32(lineWidth), $0) } } public static func fillRoundRect(x: Int, y: Int, width: Int, height: Int, radius: Int, color: Color) { color.withLCDColor { - gfx.fillRoundRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), + gfx.pointee.fillRoundRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), Int32(radius), $0) } } @@ -288,7 +288,7 @@ extension Graphics { public static func drawEllipse(x: Int, y: Int, width: Int, height: Int, lineWidth: Int, startAngle: Float = 0, endAngle: Float = 0, color: Color) { color.withLCDColor { - gfx.drawEllipse.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), + gfx.pointee.drawEllipse.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), Int32(lineWidth), startAngle, endAngle, $0) } } @@ -296,7 +296,7 @@ extension Graphics { public static func fillEllipse(x: Int, y: Int, width: Int, height: Int, startAngle: Float = 0, endAngle: Float = 0, color: Color) { color.withLCDColor { - gfx.fillEllipse.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), + gfx.pointee.fillEllipse.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), startAngle, endAngle, $0) } } @@ -313,7 +313,7 @@ extension Graphics { } color.withLCDColor { cColor in coordinates.withUnsafeMutableBufferPointer { buffer in - gfx.fillPolygon.unsafelyUnwrapped(Int32(points.count), buffer.baseAddress, + gfx.pointee.fillPolygon.unsafelyUnwrapped(Int32(points.count), buffer.baseAddress, cColor, fillRule.cValue) } } @@ -321,13 +321,13 @@ extension Graphics { /// Sets the pixel at (x, y) in the current drawing context. public static func setPixel(x: Int, y: Int, color: Color) { - color.withLCDColor { gfx.setPixel.unsafelyUnwrapped(Int32(x), Int32(y), $0) } + color.withLCDColor { gfx.pointee.setPixel.unsafelyUnwrapped(Int32(x), Int32(y), $0) } } /// Reads an 8×8 pattern from the bitmap starting at (x, y). public static func colorToPattern(from bitmap: Bitmap, x: Int, y: Int) -> Pattern { var color: LCDColor = 0 - gfx.setColorToPattern.unsafelyUnwrapped(&color, bitmap.pointer, Int32(x), Int32(y)) + gfx.pointee.setColorToPattern.unsafelyUnwrapped(&color, bitmap.pointer, Int32(x), Int32(y)) var pattern = Pattern(bytes: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) if let source = UnsafeRawPointer(bitPattern: UInt(color)) { withUnsafeMutableBytes(of: &pattern.bytes) { destination in @@ -342,41 +342,39 @@ extension Graphics { /// Draws `text` at (x, y) using the current font. Returns the drawn width. @discardableResult public static func drawText(_ text: String, x: Int, y: Int) -> Int { - let utf8 = ContiguousArray(text.utf8) - return utf8.withUnsafeBufferPointer { buffer in - Int(gfx.drawText.unsafelyUnwrapped(buffer.baseAddress, buffer.count, - kUTF8Encoding, Int32(x), Int32(y))) + text.withPlaydateUTF8 { bytes, count in + Int(gfx.pointee.drawText.unsafelyUnwrapped(bytes, count, + kUTF8Encoding, Int32(x), Int32(y))) } } /// Draws `text` wrapped and aligned inside the given rectangle. public static func drawText(_ text: String, x: Int, y: Int, width: Int, height: Int, wrap: TextWrappingMode = .word, align: TextAlignment = .left) { - let utf8 = ContiguousArray(text.utf8) - utf8.withUnsafeBufferPointer { buffer in - gfx.drawTextInRect.unsafelyUnwrapped(buffer.baseAddress, buffer.count, kUTF8Encoding, - Int32(x), Int32(y), Int32(width), Int32(height), - wrap.cValue, align.cValue) + text.withPlaydateUTF8 { bytes, count in + gfx.pointee.drawTextInRect.unsafelyUnwrapped(bytes, count, kUTF8Encoding, + Int32(x), Int32(y), Int32(width), Int32(height), + wrap.cValue, align.cValue) } } /// Sets the font used by subsequent text drawing. public static func setFont(_ font: Font) { - gfx.setFont.unsafelyUnwrapped(font.pointer) + gfx.pointee.setFont.unsafelyUnwrapped(font.pointer) } /// Extra space added between letters, in pixels. public static func setTextTracking(_ tracking: Int) { - gfx.setTextTracking.unsafelyUnwrapped(Int32(tracking)) + gfx.pointee.setTextTracking.unsafelyUnwrapped(Int32(tracking)) } public static var textTracking: Int { - Int(gfx.getTextTracking.unsafelyUnwrapped()) + Int(gfx.pointee.getTextTracking.unsafelyUnwrapped()) } /// Adjusts the line height used when drawing multi-line text. public static func setTextLeading(_ lineHeightAdjustment: Int) { - gfx.setTextLeading.unsafelyUnwrapped(Int32(lineHeightAdjustment)) + gfx.pointee.setTextLeading.unsafelyUnwrapped(Int32(lineHeightAdjustment)) } // MARK: - Framebuffer @@ -384,42 +382,42 @@ extension Graphics { /// The current working framebuffer. Rows are `rowSize` bytes. /// Call `markUpdatedRows(from:to:)` after writing directly. public static var frame: UnsafeMutablePointer? { - gfx.getFrame.unsafelyUnwrapped() + gfx.pointee.getFrame.unsafelyUnwrapped() } /// The framebuffer currently shown on the display. Rows are `rowSize` bytes. public static var displayFrame: UnsafeMutablePointer? { - gfx.getDisplayFrame.unsafelyUnwrapped() + gfx.pointee.getDisplayFrame.unsafelyUnwrapped() } /// A bitmap view of the display framebuffer. Simulator only; `nil` on device. public static var debugBitmap: Bitmap? { - guard let getDebugBitmap = gfx.getDebugBitmap, + guard let getDebugBitmap = gfx.pointee.getDebugBitmap, let pointer = getDebugBitmap() else { return nil } return Bitmap(pointer: pointer, isOwned: false) } /// A bitmap referencing the display framebuffer (not a copy). public static var displayBufferBitmap: Bitmap? { - guard let pointer = gfx.getDisplayBufferBitmap.unsafelyUnwrapped() else { return nil } + guard let pointer = gfx.pointee.getDisplayBufferBitmap.unsafelyUnwrapped() else { return nil } return Bitmap(pointer: pointer, isOwned: false) } /// A copy of the working framebuffer as a new bitmap. public static func copyFrameBufferBitmap() -> Bitmap? { - guard let pointer = gfx.copyFrameBufferBitmap.unsafelyUnwrapped() else { return nil } + guard let pointer = gfx.pointee.copyFrameBufferBitmap.unsafelyUnwrapped() else { return nil } return Bitmap(pointer: pointer, isOwned: true) } /// Tells the system which rows (inclusive) were changed by direct /// framebuffer writes and need redisplay. public static func markUpdatedRows(from start: Int, to end: Int) { - gfx.markUpdatedRows.unsafelyUnwrapped(Int32(start), Int32(end)) + gfx.pointee.markUpdatedRows.unsafelyUnwrapped(Int32(start), Int32(end)) } /// Manually flushes the framebuffer to the display. Only needed when /// drawing outside the normal update cycle. public static func display() { - gfx.display.unsafelyUnwrapped() + gfx.pointee.display.unsafelyUnwrapped() } } diff --git a/Sources/PlayDate/GraphicsBitmap.swift b/Sources/PlayDate/GraphicsBitmap.swift index b0f46de..cf0b9c8 100644 --- a/Sources/PlayDate/GraphicsBitmap.swift +++ b/Sources/PlayDate/GraphicsBitmap.swift @@ -23,7 +23,7 @@ extension Graphics { /// Allocates a new bitmap filled with `backgroundColor`. public convenience init(width: Int, height: Int, backgroundColor: Color = .clear) { let pointer = backgroundColor.withLCDColor { - gfx.newBitmap.unsafelyUnwrapped(Int32(width), Int32(height), $0) + gfx.pointee.newBitmap.unsafelyUnwrapped(Int32(width), Int32(height), $0) } self.init(pointer: pointer.unsafelyUnwrapped, isOwned: true) } @@ -31,14 +31,14 @@ extension Graphics { /// Loads a bitmap from a file in the game's pdx or Data directory. public convenience init(path: String) throws(PlaydateError) { var error: UnsafePointer? - let pointer = path.withPlaydateCString { gfx.loadBitmap.unsafelyUnwrapped($0, &error) } + let pointer = path.withPlaydateCString { gfx.pointee.loadBitmap.unsafelyUnwrapped($0, &error) } guard let pointer else { throw PlaydateError(cString: error) } self.init(pointer: pointer, isOwned: true) } deinit { if isOwned { - gfx.freeBitmap.unsafelyUnwrapped(pointer) + gfx.pointee.freeBitmap.unsafelyUnwrapped(pointer) } } @@ -58,7 +58,7 @@ extension Graphics { var width: Int32 = 0, height: Int32 = 0, rowBytes: Int32 = 0 var mask: UnsafeMutablePointer? var data: UnsafeMutablePointer? - gfx.getBitmapData.unsafelyUnwrapped(pointer, &width, &height, &rowBytes, &mask, &data) + gfx.pointee.getBitmapData.unsafelyUnwrapped(pointer, &width, &height, &rowBytes, &mask, &data) return Data(width: Int(width), height: Int(height), rowBytes: Int(rowBytes), mask: mask, data: data) } @@ -68,7 +68,7 @@ extension Graphics { /// The color of the pixel at (x, y). public func pixel(x: Int, y: Int) -> SolidColor { - SolidColor(gfx.getBitmapPixel.unsafelyUnwrapped(pointer, Int32(x), Int32(y))) + SolidColor(gfx.pointee.getBitmapPixel.unsafelyUnwrapped(pointer, Int32(x), Int32(y))) } // MARK: Operations @@ -76,23 +76,23 @@ extension Graphics { /// Replaces the bitmap's contents with the image at `path`. public func load(path: String) throws(PlaydateError) { var error: UnsafePointer? - path.withPlaydateCString { gfx.loadIntoBitmap.unsafelyUnwrapped($0, pointer, &error) } + path.withPlaydateCString { gfx.pointee.loadIntoBitmap.unsafelyUnwrapped($0, pointer, &error) } if let error { throw PlaydateError(cString: error) } } /// Fills the bitmap with `color`. public func clear(color: Color) { - color.withLCDColor { gfx.clearBitmap.unsafelyUnwrapped(pointer, $0) } + color.withLCDColor { gfx.pointee.clearBitmap.unsafelyUnwrapped(pointer, $0) } } public func copy() -> Bitmap { - Bitmap(pointer: gfx.copyBitmap.unsafelyUnwrapped(pointer).unsafelyUnwrapped, isOwned: true) + Bitmap(pointer: gfx.pointee.copyBitmap.unsafelyUnwrapped(pointer).unsafelyUnwrapped, isOwned: true) } /// Returns a new bitmap rotated by `degrees` (clockwise) and scaled. public func rotated(by degrees: Float, xScale: Float = 1, yScale: Float = 1) -> Bitmap? { var allocatedSize: Int32 = 0 - guard let rotated = gfx.rotatedBitmap.unsafelyUnwrapped( + guard let rotated = gfx.pointee.rotatedBitmap.unsafelyUnwrapped( pointer, degrees, xScale, yScale, &allocatedSize) else { return nil } return Bitmap(pointer: rotated, isOwned: true) } @@ -100,13 +100,13 @@ extension Graphics { /// Sets a mask image. The mask must match the bitmap's dimensions. @discardableResult public func setMask(_ mask: Bitmap?) -> Bool { - gfx.setBitmapMask.unsafelyUnwrapped(pointer, mask?.pointer) != 0 + gfx.pointee.setBitmapMask.unsafelyUnwrapped(pointer, mask?.pointer) != 0 } /// The bitmap's mask, if any. The returned bitmap references storage /// owned by this bitmap. public var mask: Bitmap? { - guard let mask = gfx.getBitmapMask.unsafelyUnwrapped(pointer) else { return nil } + guard let mask = gfx.pointee.getBitmapMask.unsafelyUnwrapped(pointer) else { return nil } return Bitmap(pointer: mask, isOwned: false) } @@ -116,7 +116,7 @@ extension Graphics { other: Bitmap, otherX: Int, otherY: Int, otherFlip: BitmapFlip = .unflipped, in rect: Rect) -> Bool { - gfx.checkMaskCollision.unsafelyUnwrapped( + gfx.pointee.checkMaskCollision.unsafelyUnwrapped( pointer, Int32(x), Int32(y), flip.cValue, other.pointer, Int32(otherX), Int32(otherY), otherFlip.cValue, rect.cValue) != 0 @@ -126,13 +126,13 @@ extension Graphics { /// Draws the bitmap with its upper-left corner at (x, y). public func draw(x: Int, y: Int, flip: BitmapFlip = .unflipped) { - gfx.drawBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), flip.cValue) + gfx.pointee.drawBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), flip.cValue) } /// Draws the bitmap scaled by (xScale, yScale) with its upper-left /// corner at (x, y). public func drawScaled(x: Int, y: Int, xScale: Float, yScale: Float) { - gfx.drawScaledBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), xScale, yScale) + gfx.pointee.drawScaledBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), xScale, yScale) } /// Draws the bitmap rotated by `degrees` around its anchor point, @@ -140,13 +140,13 @@ extension Graphics { public func drawRotated(x: Int, y: Int, degrees: Float, centerX: Float = 0.5, centerY: Float = 0.5, xScale: Float = 1, yScale: Float = 1) { - gfx.drawRotatedBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), degrees, + gfx.pointee.drawRotatedBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), degrees, centerX, centerY, xScale, yScale) } /// Tiles the bitmap over the given area. public func tile(x: Int, y: Int, width: Int, height: Int, flip: BitmapFlip = .unflipped) { - gfx.tileBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), + gfx.pointee.tileBitmap.unsafelyUnwrapped(pointer, Int32(x), Int32(y), Int32(width), Int32(height), flip.cValue) } } @@ -161,26 +161,26 @@ extension Graphics { /// Allocates a table with room for `count` bitmaps of the given size. public convenience init(count: Int, width: Int, height: Int) { - let pointer = gfx.newBitmapTable.unsafelyUnwrapped(Int32(count), Int32(width), Int32(height)) + let pointer = gfx.pointee.newBitmapTable.unsafelyUnwrapped(Int32(count), Int32(width), Int32(height)) self.init(pointer: pointer.unsafelyUnwrapped) } /// Loads an image table from a file. public convenience init(path: String) throws(PlaydateError) { var error: UnsafePointer? - let pointer = path.withPlaydateCString { gfx.loadBitmapTable.unsafelyUnwrapped($0, &error) } + let pointer = path.withPlaydateCString { gfx.pointee.loadBitmapTable.unsafelyUnwrapped($0, &error) } guard let pointer else { throw PlaydateError(cString: error) } self.init(pointer: pointer) } deinit { - gfx.freeBitmapTable.unsafelyUnwrapped(pointer) + gfx.pointee.freeBitmapTable.unsafelyUnwrapped(pointer) } /// Replaces the table's contents with the image table at `path`. public func load(path: String) throws(PlaydateError) { var error: UnsafePointer? - path.withPlaydateCString { gfx.loadIntoBitmapTable.unsafelyUnwrapped($0, pointer, &error) } + path.withPlaydateCString { gfx.pointee.loadIntoBitmapTable.unsafelyUnwrapped($0, pointer, &error) } if let error { throw PlaydateError(cString: error) } } @@ -188,7 +188,7 @@ extension Graphics { /// references storage owned by the table; keep the table alive while /// using it. public func bitmap(at index: Int) -> Bitmap? { - guard let bitmap = gfx.getTableBitmap.unsafelyUnwrapped(pointer, Int32(index)) else { + guard let bitmap = gfx.pointee.getTableBitmap.unsafelyUnwrapped(pointer, Int32(index)) else { return nil } return Bitmap(pointer: bitmap, isOwned: false) @@ -198,7 +198,7 @@ extension Graphics { /// of the source image. public var info: (count: Int, cellsWide: Int) { var count: Int32 = 0, width: Int32 = 0 - gfx.getBitmapTableInfo.unsafelyUnwrapped(pointer, &count, &width) + gfx.pointee.getBitmapTableInfo.unsafelyUnwrapped(pointer, &count, &width) return (Int(count), Int(width)) } diff --git a/Sources/PlayDate/GraphicsFont.swift b/Sources/PlayDate/GraphicsFont.swift index 032d6ff..68936e6 100644 --- a/Sources/PlayDate/GraphicsFont.swift +++ b/Sources/PlayDate/GraphicsFont.swift @@ -21,7 +21,7 @@ extension Graphics { /// Loads a font from a file. public convenience init(path: String) throws(PlaydateError) { var error: UnsafePointer? - let pointer = path.withPlaydateCString { gfx.loadFont.unsafelyUnwrapped($0, &error) } + let pointer = path.withPlaydateCString { gfx.pointee.loadFont.unsafelyUnwrapped($0, &error) } guard let pointer else { throw PlaydateError(cString: error) } self.init(pointer: pointer) } @@ -32,7 +32,7 @@ extension Graphics { let copy = UnsafeMutableRawPointer.allocate(byteCount: data.count, alignment: 4) copy.copyMemory(from: data.baseAddress.unsafelyUnwrapped, byteCount: data.count) let fontData = OpaquePointer(copy) - guard let pointer = gfx.makeFontFromData.unsafelyUnwrapped( + guard let pointer = gfx.pointee.makeFontFromData.unsafelyUnwrapped( fontData, wide ? 1 : 0, Int32(data.count)) else { copy.deallocate() return nil @@ -48,25 +48,23 @@ extension Graphics { /// The font's glyph height in pixels. public var height: Int { - Int(gfx.getFontHeight.unsafelyUnwrapped(pointer)) + Int(gfx.pointee.getFontHeight.unsafelyUnwrapped(pointer)) } /// The width of `text` when drawn with this font. public func textWidth(_ text: String, tracking: Int = 0) -> Int { - let utf8 = ContiguousArray(text.utf8) - return utf8.withUnsafeBufferPointer { buffer in - Int(gfx.getTextWidth.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count, - kUTF8Encoding, Int32(tracking))) + text.withPlaydateUTF8 { bytes, count in + Int(gfx.pointee.getTextWidth.unsafelyUnwrapped(pointer, bytes, count, + kUTF8Encoding, Int32(tracking))) } } /// The height of `text` when wrapped to `maxWidth` with this font. public func textHeight(_ text: String, maxWidth: Int, wrap: TextWrappingMode = .word, tracking: Int = 0, extraLeading: Int = 0) -> Int { - let utf8 = ContiguousArray(text.utf8) - return utf8.withUnsafeBufferPointer { buffer in - Int(gfx.getTextHeightForMaxWidth.unsafelyUnwrapped( - pointer, buffer.baseAddress, buffer.count, Int32(maxWidth), kUTF8Encoding, + text.withPlaydateUTF8 { bytes, count in + Int(gfx.pointee.getTextHeightForMaxWidth.unsafelyUnwrapped( + pointer, bytes, count, Int32(maxWidth), kUTF8Encoding, wrap.cValue, Int32(tracking), Int32(extraLeading))) } } @@ -74,7 +72,7 @@ extension Graphics { /// The page containing glyph data for the character `codepoint` /// belongs to. The page references data owned by the font. public func page(for codepoint: UInt32) -> FontPage? { - guard let page = gfx.getFontPage.unsafelyUnwrapped(pointer, codepoint) else { return nil } + guard let page = gfx.pointee.getFontPage.unsafelyUnwrapped(pointer, codepoint) else { return nil } return FontPage(pointer: page, font: self) } @@ -83,7 +81,7 @@ extension Graphics { public func glyph(for codepoint: UInt32) -> (glyph: Glyph, bitmap: Bitmap?, advance: Int)? { var bitmap: OpaquePointer? var advance: Int32 = 0 - guard let glyph = gfx.getFontGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else { + guard let glyph = gfx.pointee.getFontGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else { return nil } return (Glyph(pointer: glyph, font: self), @@ -102,7 +100,7 @@ extension Graphics { public func glyph(for codepoint: UInt32) -> (glyph: Glyph, bitmap: Bitmap?, advance: Int)? { var bitmap: OpaquePointer? var advance: Int32 = 0 - guard let glyph = gfx.getPageGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else { + guard let glyph = gfx.pointee.getPageGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else { return nil } return (Glyph(pointer: glyph, font: font), @@ -119,7 +117,7 @@ extension Graphics { /// The kerning adjustment between this glyph and the next character. public func kerning(glyphCode: UInt32, nextCode: UInt32) -> Int { - Int(gfx.getGlyphKerning.unsafelyUnwrapped(pointer, glyphCode, nextCode)) + Int(gfx.pointee.getGlyphKerning.unsafelyUnwrapped(pointer, glyphCode, nextCode)) } } } diff --git a/Sources/PlayDate/GraphicsTileMap.swift b/Sources/PlayDate/GraphicsTileMap.swift index 61ab691..e709497 100644 --- a/Sources/PlayDate/GraphicsTileMap.swift +++ b/Sources/PlayDate/GraphicsTileMap.swift @@ -5,7 +5,7 @@ internal import CPlaydate -private var tilemapAPI: playdate_tilemap { gfx.tilemap.pointee } +private var tilemapAPI: UnsafePointer { gfx.pointee.tilemap.unsafelyUnwrapped } extension Graphics { /// A grid of tiles drawn from a bitmap table. Wraps `LCDTileMap`. @@ -15,11 +15,11 @@ extension Graphics { private var retainedImageTable: BitmapTable? public init() { - pointer = tilemapAPI.newTilemap.unsafelyUnwrapped().unsafelyUnwrapped + pointer = tilemapAPI.pointee.newTilemap.unsafelyUnwrapped().unsafelyUnwrapped } deinit { - tilemapAPI.freeTilemap.unsafelyUnwrapped(pointer) + tilemapAPI.pointee.freeTilemap.unsafelyUnwrapped(pointer) } /// The bitmap table the tile indexes refer to. @@ -27,26 +27,26 @@ extension Graphics { get { retainedImageTable } set { retainedImageTable = newValue - tilemapAPI.setImageTable.unsafelyUnwrapped(pointer, newValue?.pointer) + tilemapAPI.pointee.setImageTable.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// Sets the tilemap's size in tiles. public func setSize(tilesWide: Int, tilesHigh: Int) { - tilemapAPI.setSize.unsafelyUnwrapped(pointer, Int32(tilesWide), Int32(tilesHigh)) + tilemapAPI.pointee.setSize.unsafelyUnwrapped(pointer, Int32(tilesWide), Int32(tilesHigh)) } /// The tilemap's size in tiles. public var size: (tilesWide: Int, tilesHigh: Int) { var wide: Int32 = 0, high: Int32 = 0 - tilemapAPI.getSize.unsafelyUnwrapped(pointer, &wide, &high) + tilemapAPI.pointee.getSize.unsafelyUnwrapped(pointer, &wide, &high) return (Int(wide), Int(high)) } /// The tilemap's total size in pixels. public var pixelSize: (width: Int, height: Int) { var width: UInt32 = 0, height: UInt32 = 0 - tilemapAPI.getPixelSize.unsafelyUnwrapped(pointer, &width, &height) + tilemapAPI.pointee.getPixelSize.unsafelyUnwrapped(pointer, &width, &height) return (Int(width), Int(height)) } @@ -55,25 +55,25 @@ extension Graphics { public func setTiles(_ indexes: [UInt16], rowWidth: Int) { var indexes = indexes indexes.withUnsafeMutableBufferPointer { buffer in - tilemapAPI.setTiles.unsafelyUnwrapped(pointer, buffer.baseAddress, + tilemapAPI.pointee.setTiles.unsafelyUnwrapped(pointer, buffer.baseAddress, Int32(buffer.count), Int32(rowWidth)) } } /// Sets the tile index at position (x, y). public func setTile(x: Int, y: Int, index: UInt16) { - tilemapAPI.setTileAtPosition.unsafelyUnwrapped(pointer, Int32(x), Int32(y), index) + tilemapAPI.pointee.setTileAtPosition.unsafelyUnwrapped(pointer, Int32(x), Int32(y), index) } /// The tile index at position (x, y), or `nil` if out of bounds. public func tile(x: Int, y: Int) -> Int? { - let index = tilemapAPI.getTileAtPosition.unsafelyUnwrapped(pointer, Int32(x), Int32(y)) + let index = tilemapAPI.pointee.getTileAtPosition.unsafelyUnwrapped(pointer, Int32(x), Int32(y)) return index < 0 ? nil : Int(index) } /// Draws the tilemap with its upper-left corner at (x, y). public func draw(x: Float, y: Float) { - tilemapAPI.drawAtPoint.unsafelyUnwrapped(pointer, x, y) + tilemapAPI.pointee.drawAtPoint.unsafelyUnwrapped(pointer, x, y) } } } diff --git a/Sources/PlayDate/GraphicsVideo.swift b/Sources/PlayDate/GraphicsVideo.swift index b2a58aa..1f6bea5 100644 --- a/Sources/PlayDate/GraphicsVideo.swift +++ b/Sources/PlayDate/GraphicsVideo.swift @@ -6,8 +6,8 @@ internal import CPlaydate -private var videoAPI: playdate_video { gfx.video.pointee } -private var streamAPI: playdate_videostream { gfx.videostream.pointee } +private var videoAPI: UnsafePointer { gfx.pointee.video.unsafelyUnwrapped } +private var streamAPI: UnsafePointer { gfx.pointee.videostream.unsafelyUnwrapped } extension Graphics { /// Plays .pdv video files. Wraps `LCDVideoPlayer`. @@ -24,7 +24,7 @@ extension Graphics { /// Opens the .pdv file at `path`. public convenience init(path: String) throws(PlaydateError) { - let pointer = path.withPlaydateCString { videoAPI.loadVideo.unsafelyUnwrapped($0) } + let pointer = path.withPlaydateCString { videoAPI.pointee.loadVideo.unsafelyUnwrapped($0) } guard let pointer else { throw PlaydateError(message: "unable to load video: \(path)") } @@ -33,13 +33,13 @@ extension Graphics { deinit { if isOwned { - videoAPI.freePlayer.unsafelyUnwrapped(pointer) + videoAPI.pointee.freePlayer.unsafelyUnwrapped(pointer) } } /// Sets the bitmap the video renders into. public func setContext(_ context: Bitmap) throws(PlaydateError) { - guard videoAPI.setContext.unsafelyUnwrapped(pointer, context.pointer) != 0 else { + guard videoAPI.pointee.setContext.unsafelyUnwrapped(pointer, context.pointer) != 0 else { throw PlaydateError(message: error ?? "unable to set video context") } retainedContext = context @@ -47,33 +47,33 @@ extension Graphics { /// The bitmap the video renders into. public var context: Bitmap? { - guard let context = videoAPI.getContext.unsafelyUnwrapped(pointer) else { return nil } + guard let context = videoAPI.pointee.getContext.unsafelyUnwrapped(pointer) else { return nil } return Bitmap(pointer: context, isOwned: false) } /// Renders directly into the display framebuffer. public func useScreenContext() { retainedContext = nil - videoAPI.useScreenContext.unsafelyUnwrapped(pointer) + videoAPI.pointee.useScreenContext.unsafelyUnwrapped(pointer) } /// Renders frame `frame` into the current context. public func renderFrame(_ frame: Int) throws(PlaydateError) { - guard videoAPI.renderFrame.unsafelyUnwrapped(pointer, Int32(frame)) != 0 else { + guard videoAPI.pointee.renderFrame.unsafelyUnwrapped(pointer, Int32(frame)) != 0 else { throw PlaydateError(message: error ?? "unable to render frame \(frame)") } } /// The most recent error message, if any. public var error: String? { - String(playdateCString: videoAPI.getError.unsafelyUnwrapped(pointer)) + String(playdateCString: videoAPI.pointee.getError.unsafelyUnwrapped(pointer)) } /// The video's dimensions, frame rate, frame count, and current frame. public var info: (width: Int, height: Int, frameRate: Float, frameCount: Int, currentFrame: Int) { var width: Int32 = 0, height: Int32 = 0, frameCount: Int32 = 0, currentFrame: Int32 = 0 var frameRate: Float = 0 - videoAPI.getInfo.unsafelyUnwrapped(pointer, &width, &height, &frameRate, + videoAPI.pointee.getInfo.unsafelyUnwrapped(pointer, &width, &height, &frameRate, &frameCount, ¤tFrame) return (Int(width), Int(height), frameRate, Int(frameCount), Int(currentFrame)) } @@ -87,62 +87,62 @@ extension Graphics { private var retainedSource: AnyObject? public init() { - pointer = streamAPI.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped + pointer = streamAPI.pointee.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped } deinit { - streamAPI.freePlayer.unsafelyUnwrapped(pointer) + streamAPI.pointee.freePlayer.unsafelyUnwrapped(pointer) } /// Sets the sizes of the stream's video and audio buffers, in bytes. public func setBufferSize(video: Int, audio: Int) { - streamAPI.setBufferSize.unsafelyUnwrapped(pointer, Int32(video), Int32(audio)) + streamAPI.pointee.setBufferSize.unsafelyUnwrapped(pointer, Int32(video), Int32(audio)) } /// Streams from an open file. public func setFile(_ file: File.Handle) { retainedSource = file - streamAPI.setFile.unsafelyUnwrapped(pointer, file.pointer) + streamAPI.pointee.setFile.unsafelyUnwrapped(pointer, file.pointer) } /// Streams from an HTTP connection. public func setHTTPConnection(_ connection: Network.HTTPConnection) { retainedSource = connection - streamAPI.setHTTPConnection.unsafelyUnwrapped(pointer, connection.pointer) + streamAPI.pointee.setHTTPConnection.unsafelyUnwrapped(pointer, connection.pointer) } /// Streams from a TCP connection. public func setTCPConnection(_ connection: Network.TCPConnection) { retainedSource = connection - streamAPI.setTCPConnection.unsafelyUnwrapped(pointer, connection.pointer) + streamAPI.pointee.setTCPConnection.unsafelyUnwrapped(pointer, connection.pointer) } /// The player used for the stream's audio track. Owned by the stream. public var filePlayer: Sound.FilePlayer? { - guard let player = streamAPI.getFilePlayer.unsafelyUnwrapped(pointer) else { return nil } + guard let player = streamAPI.pointee.getFilePlayer.unsafelyUnwrapped(pointer) else { return nil } return Sound.FilePlayer(pointer: player, isOwned: false) } /// The player used for the stream's video track. Owned by the stream. public var videoPlayer: VideoPlayer? { - guard let player = streamAPI.getVideoPlayer.unsafelyUnwrapped(pointer) else { return nil } + guard let player = streamAPI.pointee.getVideoPlayer.unsafelyUnwrapped(pointer) else { return nil } return VideoPlayer(pointer: player, isOwned: false) } /// Advances the stream. Returns `true` if a frame was drawn. @discardableResult public func update() -> Bool { - streamAPI.update.unsafelyUnwrapped(pointer) + streamAPI.pointee.update.unsafelyUnwrapped(pointer) } /// The number of video frames currently buffered. public var bufferedFrameCount: Int { - Int(streamAPI.getBufferedFrameCount.unsafelyUnwrapped(pointer)) + Int(streamAPI.pointee.getBufferedFrameCount.unsafelyUnwrapped(pointer)) } /// The total number of bytes read from the source. public var bytesRead: UInt32 { - streamAPI.getBytesRead.unsafelyUnwrapped(pointer) + streamAPI.pointee.getBytesRead.unsafelyUnwrapped(pointer) } } } diff --git a/Sources/PlayDate/JSON.swift b/Sources/PlayDate/JSON.swift index e210e45..3b89245 100644 --- a/Sources/PlayDate/JSON.swift +++ b/Sources/PlayDate/JSON.swift @@ -9,7 +9,7 @@ internal import CPlaydate -private var jsonAPI: playdate_json { Playdate.api.json.pointee } +private var jsonAPI: UnsafePointer { Playdate.jsonAPI } /// The JSON API: decoding to and encoding from a `Value` tree. public enum JSON {} @@ -112,7 +112,7 @@ extension JSON { var outval = json_value() let ok = jsonString.withPlaydateCString { cString in withExtendedLifetime(context) { - jsonAPI.decodeString.unsafelyUnwrapped(&decoder, cString, &outval) != 0 + jsonAPI.pointee.decodeString.unsafelyUnwrapped(&decoder, cString, &outval) != 0 } } guard ok else { @@ -141,7 +141,7 @@ extension JSON { var outval = json_value() let ok = withExtendedLifetime(context) { withExtendedLifetime(file) { - jsonAPI.decode.unsafelyUnwrapped(&decoder, reader, &outval) != 0 + jsonAPI.pointee.decode.unsafelyUnwrapped(&decoder, reader, &outval) != 0 } } guard ok else { @@ -172,7 +172,7 @@ extension JSON { private let output = Output() public init(pretty: Bool = false) { - jsonAPI.initEncoder.unsafelyUnwrapped(&encoder, { userdata, string, length in + jsonAPI.pointee.initEncoder.unsafelyUnwrapped(&encoder, { userdata, string, length in guard let userdata, let string else { return } let output = Unmanaged.fromOpaque(userdata).takeUnretainedValue() let bytes = UnsafeRawBufferPointer(start: string, count: Int(length)) diff --git a/Sources/PlayDate/Lua.swift b/Sources/PlayDate/Lua.swift index c3f7bd0..9211ae1 100644 --- a/Sources/PlayDate/Lua.swift +++ b/Sources/PlayDate/Lua.swift @@ -9,7 +9,7 @@ public import CPlaydate -private var luaAPI: playdate_lua { Playdate.api.lua.pointee } +private var luaAPI: UnsafePointer { Playdate.luaAPI } /// The Lua bridge: registering C functions and classes, and exchanging /// values with Lua code. @@ -59,7 +59,7 @@ extension Lua { public static func addFunction(_ function: CFunction, name: String) throws(PlaydateError) { var error: UnsafePointer? let ok = name.withPlaydateCString { - luaAPI.addFunction.unsafelyUnwrapped(function, $0, &error) != 0 + luaAPI.pointee.addFunction.unsafelyUnwrapped(function, $0, &error) != 0 } if !ok { throw PlaydateError(cString: error) } } @@ -101,7 +101,7 @@ extension Lua { var error: UnsafePointer? let ok = name.withPlaydateCString { - luaAPI.registerClass.unsafelyUnwrapped($0, registrationsBuffer, + luaAPI.pointee.registerClass.unsafelyUnwrapped($0, registrationsBuffer, values.isEmpty ? nil : constantsBuffer, isStatic ? 1 : 0, &error) != 0 } @@ -110,64 +110,64 @@ extension Lua { /// Pushes a function onto the stack, e.g. for `setUserValue`. public static func pushFunction(_ function: CFunction) { - luaAPI.pushFunction.unsafelyUnwrapped(function) + luaAPI.pointee.pushFunction.unsafelyUnwrapped(function) } /// From a class's `__index` callback: looks up the key in the instance /// metatable first. Returns 1 if a value was found. public static func indexMetatable() -> Bool { - luaAPI.indexMetatable.unsafelyUnwrapped() != 0 + luaAPI.pointee.indexMetatable.unsafelyUnwrapped() != 0 } /// Pauses the Lua runtime. public static func stop() { - luaAPI.stop.unsafelyUnwrapped() + luaAPI.pointee.stop.unsafelyUnwrapped() } /// Resumes the Lua runtime. public static func start() { - luaAPI.start.unsafelyUnwrapped() + luaAPI.pointee.start.unsafelyUnwrapped() } // MARK: - Arguments /// The number of arguments the Lua caller passed. Positions are 1-based. public static var argumentCount: Int { - Int(luaAPI.getArgCount.unsafelyUnwrapped()) + Int(luaAPI.pointee.getArgCount.unsafelyUnwrapped()) } /// The type of the argument at 1-based `position`; for objects, also the /// class name. public static func argumentType(at position: Int) -> (kind: Kind, className: String?) { var className: UnsafePointer? - let type = luaAPI.getArgType.unsafelyUnwrapped(Int32(position), &className) + let type = luaAPI.pointee.getArgType.unsafelyUnwrapped(Int32(position), &className) return (Kind(type), String(playdateCString: className)) } public static func argumentIsNil(at position: Int) -> Bool { - luaAPI.argIsNil.unsafelyUnwrapped(Int32(position)) != 0 + luaAPI.pointee.argIsNil.unsafelyUnwrapped(Int32(position)) != 0 } public static func boolArgument(at position: Int) -> Bool { - luaAPI.getArgBool.unsafelyUnwrapped(Int32(position)) != 0 + luaAPI.pointee.getArgBool.unsafelyUnwrapped(Int32(position)) != 0 } public static func intArgument(at position: Int) -> Int { - Int(luaAPI.getArgInt.unsafelyUnwrapped(Int32(position))) + Int(luaAPI.pointee.getArgInt.unsafelyUnwrapped(Int32(position))) } public static func floatArgument(at position: Int) -> Float { - luaAPI.getArgFloat.unsafelyUnwrapped(Int32(position)) + luaAPI.pointee.getArgFloat.unsafelyUnwrapped(Int32(position)) } public static func stringArgument(at position: Int) -> String? { - String(playdateCString: luaAPI.getArgString.unsafelyUnwrapped(Int32(position))) + String(playdateCString: luaAPI.pointee.getArgString.unsafelyUnwrapped(Int32(position))) } /// The argument as raw bytes (which may contain embedded zeros). public static func bytesArgument(at position: Int) -> [UInt8]? { var length = 0 - guard let bytes = luaAPI.getArgBytes.unsafelyUnwrapped(Int32(position), &length) else { + guard let bytes = luaAPI.pointee.getArgBytes.unsafelyUnwrapped(Int32(position), &length) else { return nil } let buffer = UnsafeRawBufferPointer(start: bytes, count: length) @@ -181,58 +181,58 @@ extension Lua { let cType = type.copiedPlaydateCString() defer { cType.deallocate() } var userdataObject: OpaquePointer? - let object = luaAPI.getArgObject.unsafelyUnwrapped(Int32(position), cType, &userdataObject) + let object = luaAPI.pointee.getArgObject.unsafelyUnwrapped(Int32(position), cType, &userdataObject) return (object, userdataObject.map { UDObject(pointer: $0) }) } /// The argument as a bitmap. References an object owned by Lua; retain /// the Lua value while using it. public static func bitmapArgument(at position: Int) -> Graphics.Bitmap? { - guard let bitmap = luaAPI.getBitmap.unsafelyUnwrapped(Int32(position)) else { return nil } + guard let bitmap = luaAPI.pointee.getBitmap.unsafelyUnwrapped(Int32(position)) else { return nil } return Graphics.Bitmap(pointer: bitmap, isOwned: false) } /// The argument as a sprite. public static func spriteArgument(at position: Int) -> Sprite? { - guard let sprite = luaAPI.getSprite.unsafelyUnwrapped(Int32(position)) else { return nil } + guard let sprite = luaAPI.pointee.getSprite.unsafelyUnwrapped(Int32(position)) else { return nil } return Sprite.wrapper(for: sprite) } // MARK: - Return values public static func pushNil() { - luaAPI.pushNil.unsafelyUnwrapped() + luaAPI.pointee.pushNil.unsafelyUnwrapped() } public static func push(_ value: Bool) { - luaAPI.pushBool.unsafelyUnwrapped(value ? 1 : 0) + luaAPI.pointee.pushBool.unsafelyUnwrapped(value ? 1 : 0) } public static func push(_ value: Int) { - luaAPI.pushInt.unsafelyUnwrapped(Int32(value)) + luaAPI.pointee.pushInt.unsafelyUnwrapped(Int32(value)) } public static func push(_ value: Float) { - luaAPI.pushFloat.unsafelyUnwrapped(value) + luaAPI.pointee.pushFloat.unsafelyUnwrapped(value) } public static func push(_ value: String) { - value.withPlaydateCString { luaAPI.pushString.unsafelyUnwrapped($0) } + value.withPlaydateCString { luaAPI.pointee.pushString.unsafelyUnwrapped($0) } } public static func push(bytes: [UInt8]) { bytes.withUnsafeBytes { buffer in - luaAPI.pushBytes.unsafelyUnwrapped( + luaAPI.pointee.pushBytes.unsafelyUnwrapped( buffer.baseAddress?.assumingMemoryBound(to: CChar.self), buffer.count) } } public static func push(_ bitmap: Graphics.Bitmap) { - luaAPI.pushBitmap.unsafelyUnwrapped(bitmap.pointer) + luaAPI.pointee.pushBitmap.unsafelyUnwrapped(bitmap.pointer) } public static func push(_ sprite: Sprite) { - luaAPI.pushSprite.unsafelyUnwrapped(sprite.pointer) + luaAPI.pointee.pushSprite.unsafelyUnwrapped(sprite.pointer) } /// Wraps `object` in a Lua instance of class `type` and pushes it, with @@ -242,7 +242,7 @@ extension Lua { valueCount: Int = 0) -> UDObject? { let cType = type.copiedPlaydateCString() defer { cType.deallocate() } - guard let pointer = luaAPI.pushObject.unsafelyUnwrapped(object, cType, Int32(valueCount)) else { + guard let pointer = luaAPI.pointee.pushObject.unsafelyUnwrapped(object, cType, Int32(valueCount)) else { return nil } return UDObject(pointer: pointer) @@ -255,24 +255,24 @@ extension Lua { /// Prevents the object from being garbage-collected until `release()`. @discardableResult public func retain() -> UDObject { - UDObject(pointer: luaAPI.retainObject.unsafelyUnwrapped(pointer).unsafelyUnwrapped) + UDObject(pointer: luaAPI.pointee.retainObject.unsafelyUnwrapped(pointer).unsafelyUnwrapped) } public func release() { - luaAPI.releaseObject.unsafelyUnwrapped(pointer) + luaAPI.pointee.releaseObject.unsafelyUnwrapped(pointer) } /// Pops the value on top of the stack and stores it in the object's /// user-value `slot` (1-based). public func setUserValue(slot: UInt32) { - luaAPI.setUserValue.unsafelyUnwrapped(pointer, slot) + luaAPI.pointee.setUserValue.unsafelyUnwrapped(pointer, slot) } /// Pushes the value in user-value `slot` onto the stack and returns /// its stack position, or `nil` if there is none. @discardableResult public func getUserValue(slot: UInt32) -> Int? { - let position = luaAPI.getUserValue.unsafelyUnwrapped(pointer, slot) + let position = luaAPI.pointee.getUserValue.unsafelyUnwrapped(pointer, slot) return position == 0 ? nil : Int(position) } } @@ -284,7 +284,7 @@ extension Lua { public static func callFunction(_ name: String, argumentCount: Int = 0) throws(PlaydateError) { var error: UnsafePointer? let ok = name.withPlaydateCString { - luaAPI.callFunction.unsafelyUnwrapped($0, Int32(argumentCount), &error) != 0 + luaAPI.pointee.callFunction.unsafelyUnwrapped($0, Int32(argumentCount), &error) != 0 } if !ok { throw PlaydateError(cString: error) } } diff --git a/Sources/PlayDate/Network.swift b/Sources/PlayDate/Network.swift index 9745ea7..43ac283 100644 --- a/Sources/PlayDate/Network.swift +++ b/Sources/PlayDate/Network.swift @@ -9,9 +9,9 @@ internal import CPlaydate -private var networkAPI: playdate_network { Playdate.api.network.pointee } -private var httpAPI: playdate_http { networkAPI.http.pointee } -private var tcpAPI: playdate_tcp { networkAPI.tcp.pointee } +private var networkAPI: UnsafePointer { Playdate.networkAPI } +private var httpAPI: UnsafePointer { networkAPI.pointee.http.unsafelyUnwrapped } +private var tcpAPI: UnsafePointer { networkAPI.pointee.tcp.unsafelyUnwrapped } /// The network API: wifi status, HTTP, and TCP. public enum Network {} @@ -66,7 +66,7 @@ extension Network { } public static var status: WifiStatus { - WifiStatus(rawValue: networkAPI.getStatus.unsafelyUnwrapped().rawValue) ?? .notConnected + WifiStatus(rawValue: networkAPI.pointee.getStatus.unsafelyUnwrapped().rawValue) ?? .notConnected } /// Turns the wifi radio on or off. The completion receives `nil` on @@ -74,13 +74,13 @@ extension Network { public static func setEnabled(_ enabled: Bool, completion: ((NetError?) -> Void)? = nil) { setEnabledCompletion = completion if completion != nil { - networkAPI.setEnabled.unsafelyUnwrapped(enabled, { error in + networkAPI.pointee.setEnabled.unsafelyUnwrapped(enabled, { error in let completion = Network.setEnabledCompletion Network.setEnabledCompletion = nil completion?(Network.optionalError(error)) }) } else { - networkAPI.setEnabled.unsafelyUnwrapped(enabled, nil) + networkAPI.pointee.setEnabled.unsafelyUnwrapped(enabled, nil) } } @@ -137,7 +137,7 @@ extension Network { purpose: String? = nil, completion: @escaping (Bool) -> Void) -> AccessReply { Network.requestAccess( - rawRequest: { httpAPI.requestAccess.unsafelyUnwrapped($0, $1, $2, $3, $4, $5) }, + rawRequest: { httpAPI.pointee.requestAccess.unsafelyUnwrapped($0, $1, $2, $3, $4, $5) }, server: server, port: port, useSSL: useSSL, purpose: purpose, completion: completion) } @@ -146,21 +146,21 @@ extension Network { /// granted. public init?(server: String, port: Int = 443, useSSL: Bool = true) { let pointer = server.withPlaydateCString { - httpAPI.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL) + httpAPI.pointee.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL) } guard let pointer else { return nil } self.pointer = pointer - httpAPI.setUserdata.unsafelyUnwrapped(pointer, Unmanaged.passUnretained(self).toOpaque()) + httpAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, Unmanaged.passUnretained(self).toOpaque()) } deinit { - httpAPI.setUserdata.unsafelyUnwrapped(pointer, nil) - httpAPI.release.unsafelyUnwrapped(pointer) + httpAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, nil) + httpAPI.pointee.release.unsafelyUnwrapped(pointer) } private static func wrapper(for pointer: OpaquePointer?) -> HTTPConnection? { guard let pointer, - let userdata = httpAPI.getUserdata.unsafelyUnwrapped(pointer) else { return nil } + let userdata = httpAPI.pointee.getUserdata.unsafelyUnwrapped(pointer) else { return nil } return Unmanaged.fromOpaque(userdata).takeUnretainedValue() } @@ -168,27 +168,27 @@ extension Network { /// The time to wait for the connection to open, in milliseconds. public func setConnectTimeout(milliseconds: Int) { - httpAPI.setConnectTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) + httpAPI.pointee.setConnectTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) } /// Whether to keep the connection open after a request completes. public func setKeepAlive(_ keepAlive: Bool) { - httpAPI.setKeepAlive.unsafelyUnwrapped(pointer, keepAlive) + httpAPI.pointee.setKeepAlive.unsafelyUnwrapped(pointer, keepAlive) } /// Adds a `Range: bytes=start-end` header to future requests. public func setByteRange(start: Int, end: Int) { - httpAPI.setByteRange.unsafelyUnwrapped(pointer, Int32(start), Int32(end)) + httpAPI.pointee.setByteRange.unsafelyUnwrapped(pointer, Int32(start), Int32(end)) } /// The time to wait for incoming data, in milliseconds. public func setReadTimeout(milliseconds: Int) { - httpAPI.setReadTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) + httpAPI.pointee.setReadTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) } /// The size of the connection's read buffer, in bytes. public func setReadBufferSize(bytes: Int) { - httpAPI.setReadBufferSize.unsafelyUnwrapped(pointer, Int32(bytes)) + httpAPI.pointee.setReadBufferSize.unsafelyUnwrapped(pointer, Int32(bytes)) } // MARK: Requests @@ -198,7 +198,7 @@ extension Network { public func get(path: String, headers: String = "") throws(NetError) { let error = path.withPlaydateCString { cPath in headers.withPlaydateCString { cHeaders in - httpAPI.get.unsafelyUnwrapped(pointer, cPath, cHeaders, headers.utf8.count) + httpAPI.pointee.get.unsafelyUnwrapped(pointer, cPath, cHeaders, headers.utf8.count) } } try Network.check(error) @@ -209,7 +209,7 @@ extension Network { let error = path.withPlaydateCString { cPath in headers.withPlaydateCString { cHeaders in body.withUnsafeBytes { bodyBuffer in - httpAPI.post.unsafelyUnwrapped( + httpAPI.pointee.post.unsafelyUnwrapped( pointer, cPath, cHeaders, headers.utf8.count, bodyBuffer.baseAddress?.assumingMemoryBound(to: CChar.self), bodyBuffer.count) @@ -226,7 +226,7 @@ extension Network { path.withPlaydateCString { cPath in headers.withPlaydateCString { cHeaders in body.withUnsafeBytes { bodyBuffer in - httpAPI.query.unsafelyUnwrapped( + httpAPI.pointee.query.unsafelyUnwrapped( pointer, cMethod, cPath, cHeaders, headers.utf8.count, bodyBuffer.baseAddress?.assumingMemoryBound(to: CChar.self), bodyBuffer.count) @@ -241,31 +241,31 @@ extension Network { /// The last error on the connection, if any. public var error: NetError? { - Network.optionalError(httpAPI.getError.unsafelyUnwrapped(pointer)) + Network.optionalError(httpAPI.pointee.getError.unsafelyUnwrapped(pointer)) } /// The number of bytes read of the current response, and the total /// expected (0 if the response has no Content-Length). public var progress: (read: Int, total: Int) { var read: Int32 = 0, total: Int32 = 0 - httpAPI.getProgress.unsafelyUnwrapped(pointer, &read, &total) + httpAPI.pointee.getProgress.unsafelyUnwrapped(pointer, &read, &total) return (Int(read), Int(total)) } /// The HTTP status code of the response. public var responseStatus: Int { - Int(httpAPI.getResponseStatus.unsafelyUnwrapped(pointer)) + Int(httpAPI.pointee.getResponseStatus.unsafelyUnwrapped(pointer)) } /// The number of response bytes available to read. public var bytesAvailable: Int { - Int(httpAPI.getBytesAvailable.unsafelyUnwrapped(pointer)) + Int(httpAPI.pointee.getBytesAvailable.unsafelyUnwrapped(pointer)) } /// Reads up to `buffer.count` response bytes. Returns the number of /// bytes read. public func read(into buffer: UnsafeMutableRawBufferPointer) throws(NetError) -> Int { - let result = httpAPI.read.unsafelyUnwrapped(pointer, buffer.baseAddress, + let result = httpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) if result < 0 { throw NetError(rawValue: result) ?? .unknown @@ -277,7 +277,7 @@ extension Network { public func read(length: Int) throws(NetError) -> [UInt8] { var bytes = [UInt8](repeating: 0, count: length) let result = bytes.withUnsafeMutableBytes { buffer in - httpAPI.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) + httpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) } if result < 0 { throw NetError(rawValue: result) ?? .unknown @@ -288,7 +288,7 @@ extension Network { /// Closes the connection. public func close() { - httpAPI.close.unsafelyUnwrapped(pointer) + httpAPI.pointee.close.unsafelyUnwrapped(pointer) } // MARK: Callbacks @@ -297,14 +297,14 @@ extension Network { public func setHeaderReceivedCallback(_ callback: ((HTTPConnection, _ key: String, _ value: String) -> Void)?) { headerReceivedCallback = callback if callback != nil { - httpAPI.setHeaderReceivedCallback.unsafelyUnwrapped(pointer, { connection, key, value in + httpAPI.pointee.setHeaderReceivedCallback.unsafelyUnwrapped(pointer, { connection, key, value in guard let wrapper = HTTPConnection.wrapper(for: connection), let key = String(playdateCString: key), let value = String(playdateCString: value) else { return } wrapper.headerReceivedCallback?(wrapper, key, value) }) } else { - httpAPI.setHeaderReceivedCallback.unsafelyUnwrapped(pointer, nil) + httpAPI.pointee.setHeaderReceivedCallback.unsafelyUnwrapped(pointer, nil) } } @@ -312,12 +312,12 @@ extension Network { public func setHeadersReadCallback(_ callback: ((HTTPConnection) -> Void)?) { headersReadCallback = callback if callback != nil { - httpAPI.setHeadersReadCallback.unsafelyUnwrapped(pointer, { connection in + httpAPI.pointee.setHeadersReadCallback.unsafelyUnwrapped(pointer, { connection in guard let wrapper = HTTPConnection.wrapper(for: connection) else { return } wrapper.headersReadCallback?(wrapper) }) } else { - httpAPI.setHeadersReadCallback.unsafelyUnwrapped(pointer, nil) + httpAPI.pointee.setHeadersReadCallback.unsafelyUnwrapped(pointer, nil) } } @@ -325,12 +325,12 @@ extension Network { public func setResponseCallback(_ callback: ((HTTPConnection) -> Void)?) { responseCallback = callback if callback != nil { - httpAPI.setResponseCallback.unsafelyUnwrapped(pointer, { connection in + httpAPI.pointee.setResponseCallback.unsafelyUnwrapped(pointer, { connection in guard let wrapper = HTTPConnection.wrapper(for: connection) else { return } wrapper.responseCallback?(wrapper) }) } else { - httpAPI.setResponseCallback.unsafelyUnwrapped(pointer, nil) + httpAPI.pointee.setResponseCallback.unsafelyUnwrapped(pointer, nil) } } @@ -338,12 +338,12 @@ extension Network { public func setRequestCompleteCallback(_ callback: ((HTTPConnection) -> Void)?) { requestCompleteCallback = callback if callback != nil { - httpAPI.setRequestCompleteCallback.unsafelyUnwrapped(pointer, { connection in + httpAPI.pointee.setRequestCompleteCallback.unsafelyUnwrapped(pointer, { connection in guard let wrapper = HTTPConnection.wrapper(for: connection) else { return } wrapper.requestCompleteCallback?(wrapper) }) } else { - httpAPI.setRequestCompleteCallback.unsafelyUnwrapped(pointer, nil) + httpAPI.pointee.setRequestCompleteCallback.unsafelyUnwrapped(pointer, nil) } } @@ -351,12 +351,12 @@ extension Network { public func setConnectionClosedCallback(_ callback: ((HTTPConnection) -> Void)?) { connectionClosedCallback = callback if callback != nil { - httpAPI.setConnectionClosedCallback.unsafelyUnwrapped(pointer, { connection in + httpAPI.pointee.setConnectionClosedCallback.unsafelyUnwrapped(pointer, { connection in guard let wrapper = HTTPConnection.wrapper(for: connection) else { return } wrapper.connectionClosedCallback?(wrapper) }) } else { - httpAPI.setConnectionClosedCallback.unsafelyUnwrapped(pointer, nil) + httpAPI.pointee.setConnectionClosedCallback.unsafelyUnwrapped(pointer, nil) } } } @@ -377,7 +377,7 @@ extension Network { purpose: String? = nil, completion: @escaping (Bool) -> Void) -> AccessReply { Network.requestAccess( - rawRequest: { tcpAPI.requestAccess.unsafelyUnwrapped($0, $1, $2, $3, $4, $5) }, + rawRequest: { tcpAPI.pointee.requestAccess.unsafelyUnwrapped($0, $1, $2, $3, $4, $5) }, server: server, port: port, useSSL: useSSL, purpose: purpose, completion: completion) } @@ -386,38 +386,38 @@ extension Network { /// granted. Call `open(_:)` to connect. public init?(server: String, port: Int, useSSL: Bool = true) { let pointer = server.withPlaydateCString { - tcpAPI.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL) + tcpAPI.pointee.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL) } guard let pointer else { return nil } self.pointer = pointer - tcpAPI.setUserdata.unsafelyUnwrapped(pointer, Unmanaged.passUnretained(self).toOpaque()) + tcpAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, Unmanaged.passUnretained(self).toOpaque()) } deinit { - tcpAPI.setUserdata.unsafelyUnwrapped(pointer, nil) - tcpAPI.release.unsafelyUnwrapped(pointer) + tcpAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, nil) + tcpAPI.pointee.release.unsafelyUnwrapped(pointer) } private static func wrapper(for pointer: OpaquePointer?) -> TCPConnection? { guard let pointer, - let userdata = tcpAPI.getUserdata.unsafelyUnwrapped(pointer) else { return nil } + let userdata = tcpAPI.pointee.getUserdata.unsafelyUnwrapped(pointer) else { return nil } return Unmanaged.fromOpaque(userdata).takeUnretainedValue() } /// The last error on the connection, if any. public var error: NetError? { - Network.optionalError(tcpAPI.getError.unsafelyUnwrapped(pointer)) + Network.optionalError(tcpAPI.pointee.getError.unsafelyUnwrapped(pointer)) } /// The time to wait for the connection to open, in milliseconds. public func setConnectTimeout(milliseconds: Int) { - tcpAPI.setConnectTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) + tcpAPI.pointee.setConnectTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) } /// Opens the connection. The completion receives `nil` on success. public func open(_ completion: @escaping (TCPConnection, NetError?) -> Void) throws(NetError) { openCompletion = completion - let error = tcpAPI.open.unsafelyUnwrapped(pointer, { connection, error, _ in + let error = tcpAPI.pointee.open.unsafelyUnwrapped(pointer, { connection, error, _ in guard let wrapper = TCPConnection.wrapper(for: connection) else { return } let completion = wrapper.openCompletion wrapper.openCompletion = nil @@ -428,7 +428,7 @@ extension Network { /// Closes the connection. public func close() throws(NetError) { - try Network.check(tcpAPI.close.unsafelyUnwrapped(pointer)) + try Network.check(tcpAPI.pointee.close.unsafelyUnwrapped(pointer)) } /// Called when the connection closes, with the reason if it closed @@ -436,39 +436,39 @@ extension Network { public func setConnectionClosedCallback(_ callback: ((TCPConnection, NetError?) -> Void)?) { connectionClosedCallback = callback if callback != nil { - tcpAPI.setConnectionClosedCallback.unsafelyUnwrapped(pointer, { connection, error in + tcpAPI.pointee.setConnectionClosedCallback.unsafelyUnwrapped(pointer, { connection, error in guard let wrapper = TCPConnection.wrapper(for: connection) else { return } wrapper.connectionClosedCallback?(wrapper, Network.optionalError(error)) }) } else { - tcpAPI.setConnectionClosedCallback.unsafelyUnwrapped(pointer, nil) + tcpAPI.pointee.setConnectionClosedCallback.unsafelyUnwrapped(pointer, nil) } } /// The time to wait for incoming data, in milliseconds. public func setReadTimeout(milliseconds: Int) { - tcpAPI.setReadTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) + tcpAPI.pointee.setReadTimeout.unsafelyUnwrapped(pointer, Int32(milliseconds)) } /// The size of the connection's read buffer, in bytes. public func setReadBufferSize(bytes: Int) { - tcpAPI.setReadBufferSize.unsafelyUnwrapped(pointer, Int32(bytes)) + tcpAPI.pointee.setReadBufferSize.unsafelyUnwrapped(pointer, Int32(bytes)) } /// The number of bytes available to read. public var bytesAvailable: Int { - Int(tcpAPI.getBytesAvailable.unsafelyUnwrapped(pointer)) + Int(tcpAPI.pointee.getBytesAvailable.unsafelyUnwrapped(pointer)) } /// The number of written bytes not yet sent on the wire. public var sentBytesPending: Int { - Int(tcpAPI.getSentBytesPending.unsafelyUnwrapped(pointer)) + Int(tcpAPI.pointee.getSentBytesPending.unsafelyUnwrapped(pointer)) } /// Reads up to `buffer.count` bytes, waiting up to the read timeout. /// Returns the number of bytes read. public func read(into buffer: UnsafeMutableRawBufferPointer) throws(NetError) -> Int { - let result = tcpAPI.read.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) + let result = tcpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) if result < 0 { throw NetError(rawValue: result) ?? .unknown } @@ -479,7 +479,7 @@ extension Network { public func read(length: Int) throws(NetError) -> [UInt8] { var bytes = [UInt8](repeating: 0, count: length) let result = bytes.withUnsafeMutableBytes { buffer in - tcpAPI.read.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) + tcpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) } if result < 0 { throw NetError(rawValue: result) ?? .unknown @@ -492,7 +492,7 @@ extension Network { /// accepted. @discardableResult public func write(_ buffer: UnsafeRawBufferPointer) throws(NetError) -> Int { - let result = tcpAPI.write.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) + let result = tcpAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) if result < 0 { throw NetError(rawValue: result) ?? .unknown } @@ -504,7 +504,7 @@ extension Network { @discardableResult public func write(_ bytes: [UInt8]) throws(NetError) -> Int { let result = bytes.withUnsafeBytes { buffer in - tcpAPI.write.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) + tcpAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) } if result < 0 { throw NetError(rawValue: result) ?? .unknown diff --git a/Sources/PlayDate/PlayDate.swift b/Sources/PlayDate/PlayDate.swift index 673b0c3..910d465 100644 --- a/Sources/PlayDate/PlayDate.swift +++ b/Sources/PlayDate/PlayDate.swift @@ -24,6 +24,19 @@ public enum Playdate { /// need to pass the `PlaydateAPI*` back to C. public internal(set) nonisolated(unsafe) static var apiPointer: UnsafeMutablePointer! + // Sub-API pointers cached once at initialization, so wrapper calls are a + // single field load off a pointer instead of re-walking `api` per call. + nonisolated(unsafe) static var systemAPI: UnsafePointer! + nonisolated(unsafe) static var displayAPI: UnsafePointer! + nonisolated(unsafe) static var graphicsAPI: UnsafePointer! + nonisolated(unsafe) static var spriteAPI: UnsafePointer! + nonisolated(unsafe) static var soundAPI: UnsafePointer! + nonisolated(unsafe) static var fileAPI: UnsafePointer! + nonisolated(unsafe) static var jsonAPI: UnsafePointer! + nonisolated(unsafe) static var luaAPI: UnsafePointer! + nonisolated(unsafe) static var scoreboardsAPI: UnsafePointer! + nonisolated(unsafe) static var networkAPI: UnsafePointer! + /// Stores the API pointer handed to the game's `eventHandler`. /// /// Call this first, on the `.initialize` event, before using any other @@ -31,6 +44,16 @@ public enum Playdate { public static func initialize(with pointer: UnsafeMutableRawPointer) { apiPointer = pointer.assumingMemoryBound(to: PlaydateAPI.self) api = apiPointer.pointee + systemAPI = api.system + displayAPI = api.display + graphicsAPI = api.graphics + spriteAPI = api.sprite + soundAPI = api.sound + fileAPI = api.file + jsonAPI = api.json + luaAPI = api.lua + scoreboardsAPI = api.scoreboards + networkAPI = api.network } } diff --git a/Sources/PlayDate/Scoreboards.swift b/Sources/PlayDate/Scoreboards.swift index 2237347..922da13 100644 --- a/Sources/PlayDate/Scoreboards.swift +++ b/Sources/PlayDate/Scoreboards.swift @@ -9,7 +9,7 @@ internal import CPlaydate -private var scoreboardsAPI: playdate_scoreboards { Playdate.api.scoreboards.pointee } +private var scoreboardsAPI: UnsafePointer { Playdate.scoreboardsAPI } /// The scoreboards API for games with online leaderboards. public enum Scoreboards {} @@ -102,7 +102,7 @@ extension Scoreboards { completion: @escaping (Result) -> Void) -> Bool { addScoreCompletion = completion return boardID.withPlaydateCString { cBoardID in - scoreboardsAPI.addScore.unsafelyUnwrapped(cBoardID, value, { score, errorMessage in + scoreboardsAPI.pointee.addScore.unsafelyUnwrapped(cBoardID, value, { score, errorMessage in let completion = Scoreboards.addScoreCompletion Scoreboards.addScoreCompletion = nil completion?(Scoreboards.result(score, errorMessage)) @@ -116,7 +116,7 @@ extension Scoreboards { completion: @escaping (Result) -> Void) -> Bool { personalBestCompletion = completion return boardID.withPlaydateCString { cBoardID in - scoreboardsAPI.getPersonalBest.unsafelyUnwrapped(cBoardID, { score, errorMessage in + scoreboardsAPI.pointee.getPersonalBest.unsafelyUnwrapped(cBoardID, { score, errorMessage in let completion = Scoreboards.personalBestCompletion Scoreboards.personalBestCompletion = nil completion?(Scoreboards.result(score, errorMessage)) @@ -128,7 +128,7 @@ extension Scoreboards { @discardableResult public static func getScoreboards(completion: @escaping (Result) -> Void) -> Bool { boardsCompletion = completion - return scoreboardsAPI.getScoreboards.unsafelyUnwrapped({ boards, errorMessage in + return scoreboardsAPI.pointee.getScoreboards.unsafelyUnwrapped({ boards, errorMessage in let completion = Scoreboards.boardsCompletion Scoreboards.boardsCompletion = nil guard let boards else { @@ -136,7 +136,7 @@ extension Scoreboards { return } let list = BoardsList(boards.pointee) - scoreboardsAPI.freeBoardsList.unsafelyUnwrapped(boards) + scoreboardsAPI.pointee.freeBoardsList.unsafelyUnwrapped(boards) completion?(.success(list)) }) != 0 } @@ -147,7 +147,7 @@ extension Scoreboards { completion: @escaping (Result) -> Void) -> Bool { scoresCompletion = completion return boardID.withPlaydateCString { cBoardID in - scoreboardsAPI.getScores.unsafelyUnwrapped(cBoardID, { scores, errorMessage in + scoreboardsAPI.pointee.getScores.unsafelyUnwrapped(cBoardID, { scores, errorMessage in let completion = Scoreboards.scoresCompletion Scoreboards.scoresCompletion = nil guard let scores else { @@ -155,7 +155,7 @@ extension Scoreboards { return } let list = ScoresList(scores.pointee) - scoreboardsAPI.freeScoresList.unsafelyUnwrapped(scores) + scoreboardsAPI.pointee.freeScoresList.unsafelyUnwrapped(scores) completion?(.success(list)) }) != 0 } @@ -167,7 +167,7 @@ extension Scoreboards { return .failure(PlaydateError(cString: errorMessage)) } let value = Score(score.pointee) - scoreboardsAPI.freeScore.unsafelyUnwrapped(score) + scoreboardsAPI.pointee.freeScore.unsafelyUnwrapped(score) return .success(value) } } diff --git a/Sources/PlayDate/Sound.swift b/Sources/PlayDate/Sound.swift index fcb7b08..5de54cb 100644 --- a/Sources/PlayDate/Sound.swift +++ b/Sources/PlayDate/Sound.swift @@ -7,7 +7,7 @@ internal import CPlaydate -var snd: playdate_sound { Playdate.api.sound.pointee } +var snd: UnsafePointer { Playdate.soundAPI } /// The sound API: channels, players, synths, sequences, and effects. public enum Sound {} @@ -60,25 +60,25 @@ extension Sound { /// The most recent sound error as a thrown error. static func lastError() -> PlaydateError { - PlaydateError(cString: snd.getError.unsafelyUnwrapped()) + PlaydateError(cString: snd.pointee.getError.unsafelyUnwrapped()) } // MARK: - Top-level functions /// The audio engine's current time, in frames (44,100 per second). public static var currentTime: UInt32 { - snd.getCurrentTime.unsafelyUnwrapped() + snd.pointee.getCurrentTime.unsafelyUnwrapped() } /// The most recent audio error message, if any. public static var error: String? { - String(playdateCString: snd.getError.unsafelyUnwrapped()) + String(playdateCString: snd.pointee.getError.unsafelyUnwrapped()) } /// Removes a source from its channel. @discardableResult public static func removeSource(_ source: Source) -> Bool { - let removed = snd.removeSource.unsafelyUnwrapped(source.pointer) != 0 + let removed = snd.pointee.removeSource.unsafelyUnwrapped(source.pointer) != 0 CallbackSource.release(source) return removed } @@ -91,12 +91,12 @@ extension Sound { _ callback: ((UnsafeMutableBufferPointer) -> Bool)?) -> Bool { micCallback = callback if callback != nil { - return snd.setMicCallback.unsafelyUnwrapped({ _, buffer, length in + return snd.pointee.setMicCallback.unsafelyUnwrapped({ _, buffer, length in let samples = UnsafeMutableBufferPointer(start: buffer, count: Int(length)) return Sound.micCallback?(samples) == true ? 1 : 0 }, nil, CPlaydate.MicSource(source.rawValue)) != 0 } else { - return snd.setMicCallback.unsafelyUnwrapped(nil, nil, CPlaydate.MicSource(source.rawValue)) != 0 + return snd.pointee.setMicCallback.unsafelyUnwrapped(nil, nil, CPlaydate.MicSource(source.rawValue)) != 0 } } @@ -119,10 +119,10 @@ extension Sound { let reply: accessReply if let purpose { reply = purpose.withPlaydateCString { - snd.requestMicAccess.unsafelyUnwrapped($0, trampoline, box.toOpaque()) + snd.pointee.requestMicAccess.unsafelyUnwrapped($0, trampoline, box.toOpaque()) } } else { - reply = snd.requestMicAccess.unsafelyUnwrapped(nil, trampoline, box.toOpaque()) + reply = snd.pointee.requestMicAccess.unsafelyUnwrapped(nil, trampoline, box.toOpaque()) } if reply != kAccessAsk { // The callback will not be invoked; balance the retain. @@ -134,7 +134,7 @@ extension Sound { /// The current headphone and headset-microphone state. public static var headphoneState: (headphone: Bool, headsetMic: Bool) { var headphone: Int32 = 0, headsetMic: Int32 = 0 - snd.getHeadphoneState.unsafelyUnwrapped(&headphone, &headsetMic, nil) + snd.pointee.getHeadphoneState.unsafelyUnwrapped(&headphone, &headsetMic, nil) return (headphone != 0, headsetMic != 0) } @@ -143,11 +143,11 @@ extension Sound { public static func setHeadphoneChangeCallback(_ callback: ((_ headphone: Bool, _ headsetMic: Bool) -> Void)?) { headphoneChangeCallback = callback if callback != nil { - snd.getHeadphoneState.unsafelyUnwrapped(nil, nil, { headphone, mic in + snd.pointee.getHeadphoneState.unsafelyUnwrapped(nil, nil, { headphone, mic in Sound.headphoneChangeCallback?(headphone != 0, mic != 0) }) } else { - snd.getHeadphoneState.unsafelyUnwrapped(nil, nil, nil) + snd.pointee.getHeadphoneState.unsafelyUnwrapped(nil, nil, nil) } } @@ -157,7 +157,7 @@ extension Sound { /// headphone jack drives output and `speaker` is also set, the speaker /// plays too. public static func setOutputsActive(headphone: Bool, speaker: Bool) { - snd.setOutputsActive.unsafelyUnwrapped(headphone ? 1 : 0, speaker ? 1 : 0) + snd.pointee.setOutputsActive.unsafelyUnwrapped(headphone ? 1 : 0, speaker ? 1 : 0) } /// Adds a callback-based source to the default channel. The callback @@ -166,7 +166,7 @@ extension Sound { public static func addSource(stereo: Bool, _ callback: @escaping CallbackSource.Callback) -> CallbackSource { let source = CallbackSource(callback: callback) - let pointer = snd.addSource.unsafelyUnwrapped( + let pointer = snd.pointee.addSource.unsafelyUnwrapped( CallbackSource.trampoline, source.contextPointer, stereo ? 1 : 0) source.adopt(pointer: pointer.unsafelyUnwrapped) return source @@ -176,7 +176,7 @@ extension Sound { /// A mixer channel holding sources and effects. Wraps `SoundChannel`. public final class Channel { - private static var api: playdate_sound_channel { snd.channel.pointee } + private static var api: UnsafePointer { snd.pointee.channel.unsafelyUnwrapped } let pointer: OpaquePointer let isOwned: Bool @@ -191,13 +191,13 @@ extension Sound { /// Creates a new channel. Add it to the sound engine with `add()`. public convenience init() { - self.init(pointer: Channel.api.newChannel.unsafelyUnwrapped().unsafelyUnwrapped, + self.init(pointer: Channel.api.pointee.newChannel.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - Channel.api.freeChannel.unsafelyUnwrapped(pointer) + Channel.api.pointee.freeChannel.unsafelyUnwrapped(pointer) // The freed channel no longer pulls its callback sources, so // their trampoline registrations can be released too. for source in retainedSources where source is CallbackSource { @@ -209,7 +209,7 @@ extension Sound { /// The default channel, which sources are added to unless otherwise /// specified. public static var `default`: Channel { - Channel(pointer: snd.getDefaultChannel.unsafelyUnwrapped().unsafelyUnwrapped, + Channel(pointer: snd.pointee.getDefaultChannel.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: false) } @@ -218,7 +218,7 @@ extension Sound { /// Adds the channel to the sound engine. @discardableResult public func add() -> Bool { - let added = snd.addChannel.unsafelyUnwrapped(pointer) != 0 + let added = snd.pointee.addChannel.unsafelyUnwrapped(pointer) != 0 if added, !Channel.addedChannels.contains(where: { $0 === self }) { Channel.addedChannels.append(self) } @@ -228,7 +228,7 @@ extension Sound { /// Removes the channel from the sound engine. @discardableResult public func remove() -> Bool { - let removed = snd.removeChannel.unsafelyUnwrapped(pointer) != 0 + let removed = snd.pointee.removeChannel.unsafelyUnwrapped(pointer) != 0 Channel.addedChannels.removeAll { $0 === self } return removed } @@ -236,7 +236,7 @@ extension Sound { /// Adds a source to the channel. A source can only be on one channel. @discardableResult public func addSource(_ source: Source) -> Bool { - let added = Channel.api.addSource.unsafelyUnwrapped(pointer, source.pointer) != 0 + let added = Channel.api.pointee.addSource.unsafelyUnwrapped(pointer, source.pointer) != 0 if added, !retainedSources.contains(where: { $0 === source }) { retainedSources.append(source) } @@ -245,7 +245,7 @@ extension Sound { @discardableResult public func removeSource(_ source: Source) -> Bool { - let removed = Channel.api.removeSource.unsafelyUnwrapped(pointer, source.pointer) != 0 + let removed = Channel.api.pointee.removeSource.unsafelyUnwrapped(pointer, source.pointer) != 0 retainedSources.removeAll { $0 === source } CallbackSource.release(source) return removed @@ -256,7 +256,7 @@ extension Sound { public func addCallbackSource(stereo: Bool, _ callback: @escaping CallbackSource.Callback) -> CallbackSource { let source = CallbackSource(callback: callback) - let pointer = Channel.api.addCallbackSource.unsafelyUnwrapped( + let pointer = Channel.api.pointee.addCallbackSource.unsafelyUnwrapped( self.pointer, CallbackSource.trampoline, source.contextPointer, stereo ? 1 : 0) source.adopt(pointer: pointer.unsafelyUnwrapped) retainedSources.append(source) @@ -265,7 +265,7 @@ extension Sound { @discardableResult public func addEffect(_ effect: Effect) -> Bool { - let added = Channel.api.addEffect.unsafelyUnwrapped(pointer, effect.pointer) != 0 + let added = Channel.api.pointee.addEffect.unsafelyUnwrapped(pointer, effect.pointer) != 0 if added, !retainedEffects.contains(where: { $0 === effect }) { retainedEffects.append(effect) } @@ -274,56 +274,56 @@ extension Sound { @discardableResult public func removeEffect(_ effect: Effect) -> Bool { - let removed = Channel.api.removeEffect.unsafelyUnwrapped(pointer, effect.pointer) != 0 + let removed = Channel.api.pointee.removeEffect.unsafelyUnwrapped(pointer, effect.pointer) != 0 retainedEffects.removeAll { $0 === effect } return removed } /// The channel's volume, 0...1. public var volume: Float { - get { Channel.api.getVolume.unsafelyUnwrapped(pointer) } - set { Channel.api.setVolume.unsafelyUnwrapped(pointer, newValue) } + get { Channel.api.pointee.getVolume.unsafelyUnwrapped(pointer) } + set { Channel.api.pointee.setVolume.unsafelyUnwrapped(pointer, newValue) } } /// Modulates the channel's volume. public func setVolumeModulator(_ modulator: SignalValue?) { retain(modulator) - Channel.api.setVolumeModulator.unsafelyUnwrapped(pointer, modulator?.pointer) + Channel.api.pointee.setVolumeModulator.unsafelyUnwrapped(pointer, modulator?.pointer) } public var volumeModulator: SignalValue? { - SignalValue.wrap(Channel.api.getVolumeModulator.unsafelyUnwrapped(pointer)) + SignalValue.wrap(Channel.api.pointee.getVolumeModulator.unsafelyUnwrapped(pointer)) } /// The channel's stereo pan: -1 (left) to 1 (right). public func setPan(_ pan: Float) { - Channel.api.setPan.unsafelyUnwrapped(pointer, pan) + Channel.api.pointee.setPan.unsafelyUnwrapped(pointer, pan) } /// Modulates the channel's pan. The signal's range 0...1 maps to /// left...right. public func setPanModulator(_ modulator: SignalValue?) { retain(modulator) - Channel.api.setPanModulator.unsafelyUnwrapped(pointer, modulator?.pointer) + Channel.api.pointee.setPanModulator.unsafelyUnwrapped(pointer, modulator?.pointer) } public var panModulator: SignalValue? { - SignalValue.wrap(Channel.api.getPanModulator.unsafelyUnwrapped(pointer)) + SignalValue.wrap(Channel.api.pointee.getPanModulator.unsafelyUnwrapped(pointer)) } /// A signal following the channel's dry (unprocessed) level. public var dryLevelSignal: SignalValue? { - SignalValue.wrap(Channel.api.getDryLevelSignal.unsafelyUnwrapped(pointer)) + SignalValue.wrap(Channel.api.pointee.getDryLevelSignal.unsafelyUnwrapped(pointer)) } /// A signal following the channel's wet (processed) level. public var wetLevelSignal: SignalValue? { - SignalValue.wrap(Channel.api.getWetLevelSignal.unsafelyUnwrapped(pointer)) + SignalValue.wrap(Channel.api.pointee.getWetLevelSignal.unsafelyUnwrapped(pointer)) } /// The channel's output as a source, for feeding into another channel. public var outputAsSource: Source? { - guard let source = Channel.api.getOutputAsSource.unsafelyUnwrapped(pointer) else { + guard let source = Channel.api.pointee.getOutputAsSource.unsafelyUnwrapped(pointer) else { return nil } return Source(pointer: source, isOwned: false) diff --git a/Sources/PlayDate/SoundEffect.swift b/Sources/PlayDate/SoundEffect.swift index 722259c..dada564 100644 --- a/Sources/PlayDate/SoundEffect.swift +++ b/Sources/PlayDate/SoundEffect.swift @@ -6,7 +6,7 @@ internal import CPlaydate -private var effectAPI: playdate_sound_effect { snd.effect.pointee } +private var effectAPI: UnsafePointer { snd.pointee.effect.unsafelyUnwrapped } extension Sound { /// An effect that processes a channel's audio: the base class of the @@ -38,9 +38,9 @@ extension Sound { public init(processor: @escaping Processor) { let box = Unmanaged.passRetained(ProcessorBox(processor)) processorBox = box - pointer = effectAPI.newEffect.unsafelyUnwrapped({ effect, left, right, nsamples, bufactive in + pointer = effectAPI.pointee.newEffect.unsafelyUnwrapped({ effect, left, right, nsamples, bufactive in guard let effect, let left, - let userdata = effectAPI.getUserdata.unsafelyUnwrapped(effect) else { return 0 } + let userdata = effectAPI.pointee.getUserdata.unsafelyUnwrapped(effect) else { return 0 } let box = Unmanaged.fromOpaque(userdata).takeUnretainedValue() let leftBuffer = UnsafeMutableBufferPointer(start: left, count: Int(nsamples)) let rightBuffer = right.map { UnsafeMutableBufferPointer(start: $0, count: Int(nsamples)) } @@ -51,21 +51,21 @@ extension Sound { deinit { if isOwned { - effectAPI.freeEffect.unsafelyUnwrapped(pointer) + effectAPI.pointee.freeEffect.unsafelyUnwrapped(pointer) } processorBox?.release() } /// The wet/dry mix: 1 is fully processed, 0 fully dry. public func setMix(_ level: Float) { - effectAPI.setMix.unsafelyUnwrapped(pointer, level) + effectAPI.pointee.setMix.unsafelyUnwrapped(pointer, level) } public var mixModulator: SignalValue? { - get { SignalValue.wrap(effectAPI.getMixModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(effectAPI.pointee.getMixModulator.unsafelyUnwrapped(pointer)) } set { retainedMixModulator = newValue - effectAPI.setMixModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + effectAPI.pointee.setMixModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } } @@ -74,7 +74,7 @@ extension Sound { /// A two-pole IIR filter. Wraps `TwoPoleFilter`. public final class TwoPoleFilter: Effect { - private static var api: playdate_sound_effect_twopolefilter { effectAPI.twopolefilter.pointee } + private static var api: UnsafePointer { effectAPI.pointee.twopolefilter.unsafelyUnwrapped } public enum Kind: UInt32, Sendable { case lowPass = 0 @@ -92,48 +92,48 @@ extension Sound { private var retainedResonanceModulator: SignalValue? public init(kind: Kind = .lowPass) { - super.init(pointer: TwoPoleFilter.api.newFilter.unsafelyUnwrapped().unsafelyUnwrapped, + super.init(pointer: TwoPoleFilter.api.pointee.newFilter.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) setKind(kind) } deinit { if isOwned { - TwoPoleFilter.api.freeFilter.unsafelyUnwrapped(pointer) + TwoPoleFilter.api.pointee.freeFilter.unsafelyUnwrapped(pointer) } } public func setKind(_ kind: Kind) { - TwoPoleFilter.api.setType.unsafelyUnwrapped(pointer, kind.cValue) + TwoPoleFilter.api.pointee.setType.unsafelyUnwrapped(pointer, kind.cValue) } /// The center/corner frequency, in Hz. public func setFrequency(_ frequency: Float) { - TwoPoleFilter.api.setFrequency.unsafelyUnwrapped(pointer, frequency) + TwoPoleFilter.api.pointee.setFrequency.unsafelyUnwrapped(pointer, frequency) } public var frequencyModulator: SignalValue? { - get { SignalValue.wrap(TwoPoleFilter.api.getFrequencyModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(TwoPoleFilter.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) } set { retainedFrequencyModulator = newValue - TwoPoleFilter.api.setFrequencyModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + TwoPoleFilter.api.pointee.setFrequencyModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// The gain, used by PEQ and shelf filters. public func setGain(_ gain: Float) { - TwoPoleFilter.api.setGain.unsafelyUnwrapped(pointer, gain) + TwoPoleFilter.api.pointee.setGain.unsafelyUnwrapped(pointer, gain) } public func setResonance(_ resonance: Float) { - TwoPoleFilter.api.setResonance.unsafelyUnwrapped(pointer, resonance) + TwoPoleFilter.api.pointee.setResonance.unsafelyUnwrapped(pointer, resonance) } public var resonanceModulator: SignalValue? { - get { SignalValue.wrap(TwoPoleFilter.api.getResonanceModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(TwoPoleFilter.api.pointee.getResonanceModulator.unsafelyUnwrapped(pointer)) } set { retainedResonanceModulator = newValue - TwoPoleFilter.api.setResonanceModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + TwoPoleFilter.api.pointee.setResonanceModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } } @@ -142,32 +142,32 @@ extension Sound { /// A one-pole low/high-pass filter. Wraps `OnePoleFilter`. public final class OnePoleFilter: Effect { - private static var api: playdate_sound_effect_onepolefilter { effectAPI.onepolefilter.pointee } + private static var api: UnsafePointer { effectAPI.pointee.onepolefilter.unsafelyUnwrapped } private var retainedParameterModulator: SignalValue? public init() { - super.init(pointer: OnePoleFilter.api.newFilter.unsafelyUnwrapped().unsafelyUnwrapped, + super.init(pointer: OnePoleFilter.api.pointee.newFilter.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - OnePoleFilter.api.freeFilter.unsafelyUnwrapped(pointer) + OnePoleFilter.api.pointee.freeFilter.unsafelyUnwrapped(pointer) } } /// The filter's cutoff: -1 to 1, where values above 0 are low-pass /// and values below 0 high-pass. public func setParameter(_ parameter: Float) { - OnePoleFilter.api.setParameter.unsafelyUnwrapped(pointer, parameter) + OnePoleFilter.api.pointee.setParameter.unsafelyUnwrapped(pointer, parameter) } public var parameterModulator: SignalValue? { - get { SignalValue.wrap(OnePoleFilter.api.getParameterModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(OnePoleFilter.api.pointee.getParameterModulator.unsafelyUnwrapped(pointer)) } set { retainedParameterModulator = newValue - OnePoleFilter.api.setParameterModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + OnePoleFilter.api.pointee.setParameterModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } } @@ -176,49 +176,49 @@ extension Sound { /// A bit-crushing and downsampling effect. Wraps `BitCrusher`. public final class BitCrusher: Effect { - private static var api: playdate_sound_effect_bitcrusher { effectAPI.bitcrusher.pointee } + private static var api: UnsafePointer { effectAPI.pointee.bitcrusher.unsafelyUnwrapped } private var retainedModulators: [SignalValue] = [] public init() { - super.init(pointer: BitCrusher.api.newBitCrusher.unsafelyUnwrapped().unsafelyUnwrapped, + super.init(pointer: BitCrusher.api.pointee.newBitCrusher.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - BitCrusher.api.freeBitCrusher.unsafelyUnwrapped(pointer) + BitCrusher.api.pointee.freeBitCrusher.unsafelyUnwrapped(pointer) } } /// When `true`, `setDepth` values map exponentially to bit depth. public func setExponential(_ flag: Bool) { - BitCrusher.api.setExponential.unsafelyUnwrapped(pointer, flag) + BitCrusher.api.pointee.setExponential.unsafelyUnwrapped(pointer, flag) } /// The amount of crushing, 0 (none) to 1 (quantized to 1 bit). public func setDepth(_ depth: Float) { - BitCrusher.api.setDepth.unsafelyUnwrapped(pointer, depth) + BitCrusher.api.pointee.setDepth.unsafelyUnwrapped(pointer, depth) } public var depthModulator: SignalValue? { - get { SignalValue.wrap(BitCrusher.api.getDepthModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(BitCrusher.api.pointee.getDepthModulator.unsafelyUnwrapped(pointer)) } set { retain(newValue) - BitCrusher.api.setDepthModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + BitCrusher.api.pointee.setDepthModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// The amount of downsampling, 0 (none) to 1 (every sample repeated). public func setDownsampling(_ downsampling: Float) { - BitCrusher.api.setDownsampling.unsafelyUnwrapped(pointer, downsampling) + BitCrusher.api.pointee.setDownsampling.unsafelyUnwrapped(pointer, downsampling) } public var downsamplingModulator: SignalValue? { - get { SignalValue.wrap(BitCrusher.api.getDownsamplingModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(BitCrusher.api.pointee.getDownsamplingModulator.unsafelyUnwrapped(pointer)) } set { retain(newValue) - BitCrusher.api.setDownsamplingModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + BitCrusher.api.pointee.setDownsamplingModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } @@ -231,31 +231,31 @@ extension Sound { /// A ring modulator effect. Wraps `RingModulator`. public final class RingModulator: Effect { - private static var api: playdate_sound_effect_ringmodulator { effectAPI.ringmodulator.pointee } + private static var api: UnsafePointer { effectAPI.pointee.ringmodulator.unsafelyUnwrapped } private var retainedFrequencyModulator: SignalValue? public init() { - super.init(pointer: RingModulator.api.newRingmod.unsafelyUnwrapped().unsafelyUnwrapped, + super.init(pointer: RingModulator.api.pointee.newRingmod.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - RingModulator.api.freeRingmod.unsafelyUnwrapped(pointer) + RingModulator.api.pointee.freeRingmod.unsafelyUnwrapped(pointer) } } /// The modulation frequency, in Hz. public func setFrequency(_ frequency: Float) { - RingModulator.api.setFrequency.unsafelyUnwrapped(pointer, frequency) + RingModulator.api.pointee.setFrequency.unsafelyUnwrapped(pointer, frequency) } public var frequencyModulator: SignalValue? { - get { SignalValue.wrap(RingModulator.api.getFrequencyModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(RingModulator.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) } set { retainedFrequencyModulator = newValue - RingModulator.api.setFrequencyModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + RingModulator.api.pointee.setFrequencyModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } } @@ -265,7 +265,7 @@ extension Sound { /// A tap into a delay line; produces audio and can be added to a channel /// as a source. Wraps `DelayLineTap`. public final class DelayLineTap: Source { - private static var api: playdate_sound_effect_delayline { effectAPI.delayline.pointee } + private static var api: UnsafePointer { effectAPI.pointee.delayline.unsafelyUnwrapped } /// The delay line is retained so the tap stays valid. private let delayLine: DelayLine @@ -277,59 +277,59 @@ extension Sound { } deinit { - DelayLineTap.api.freeTap.unsafelyUnwrapped(pointer) + DelayLineTap.api.pointee.freeTap.unsafelyUnwrapped(pointer) } /// The tap's position in the delay line, in frames. public func setDelay(frames: Int) { - DelayLineTap.api.setTapDelay.unsafelyUnwrapped(pointer, Int32(frames)) + DelayLineTap.api.pointee.setTapDelay.unsafelyUnwrapped(pointer, Int32(frames)) } public var delayModulator: SignalValue? { - get { SignalValue.wrap(DelayLineTap.api.getTapDelayModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(DelayLineTap.api.pointee.getTapDelayModulator.unsafelyUnwrapped(pointer)) } set { retainedDelayModulator = newValue - DelayLineTap.api.setTapDelayModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + DelayLineTap.api.pointee.setTapDelayModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// For stereo delay lines: swaps the left and right channels. public func setChannelsFlipped(_ flipped: Bool) { - DelayLineTap.api.setTapChannelsFlipped.unsafelyUnwrapped(pointer, flipped ? 1 : 0) + DelayLineTap.api.pointee.setTapChannelsFlipped.unsafelyUnwrapped(pointer, flipped ? 1 : 0) } } /// A delay line effect. Wraps `DelayLine`. public final class DelayLine: Effect { - private static var api: playdate_sound_effect_delayline { effectAPI.delayline.pointee } + private static var api: UnsafePointer { effectAPI.pointee.delayline.unsafelyUnwrapped } /// Creates a delay line holding `length` frames. public init(length: Int, stereo: Bool = false) { - super.init(pointer: DelayLine.api.newDelayLine.unsafelyUnwrapped( + super.init(pointer: DelayLine.api.pointee.newDelayLine.unsafelyUnwrapped( Int32(length), stereo ? 1 : 0).unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - DelayLine.api.freeDelayLine.unsafelyUnwrapped(pointer) + DelayLine.api.pointee.freeDelayLine.unsafelyUnwrapped(pointer) } } /// Changes the delay length. Cannot be larger than the line's /// original length. public func setLength(frames: Int) { - DelayLine.api.setLength.unsafelyUnwrapped(pointer, Int32(frames)) + DelayLine.api.pointee.setLength.unsafelyUnwrapped(pointer, Int32(frames)) } /// The feedback level, 0...1. public func setFeedback(_ feedback: Float) { - DelayLine.api.setFeedback.unsafelyUnwrapped(pointer, feedback) + DelayLine.api.pointee.setFeedback.unsafelyUnwrapped(pointer, feedback) } /// Adds a tap `delay` frames behind the write head. The tap can be /// added to a channel as a sound source. public func addTap(delay: Int) -> DelayLineTap? { - guard let tap = DelayLine.api.addTap.unsafelyUnwrapped(pointer, Int32(delay)) else { + guard let tap = DelayLine.api.pointee.addTap.unsafelyUnwrapped(pointer, Int32(delay)) else { return nil } return DelayLineTap(pointer: tap, delayLine: self) @@ -340,49 +340,49 @@ extension Sound { /// An overdrive/distortion effect. Wraps `Overdrive`. public final class Overdrive: Effect { - private static var api: playdate_sound_effect_overdrive { effectAPI.overdrive.pointee } + private static var api: UnsafePointer { effectAPI.pointee.overdrive.unsafelyUnwrapped } private var retainedModulators: [SignalValue] = [] public init() { - super.init(pointer: Overdrive.api.newOverdrive.unsafelyUnwrapped().unsafelyUnwrapped, + super.init(pointer: Overdrive.api.pointee.newOverdrive.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - Overdrive.api.freeOverdrive.unsafelyUnwrapped(pointer) + Overdrive.api.pointee.freeOverdrive.unsafelyUnwrapped(pointer) } } /// The input gain applied before clipping. public func setGain(_ gain: Float) { - Overdrive.api.setGain.unsafelyUnwrapped(pointer, gain) + Overdrive.api.pointee.setGain.unsafelyUnwrapped(pointer, gain) } /// The level where the amplified input clips. public func setLimit(_ limit: Float) { - Overdrive.api.setLimit.unsafelyUnwrapped(pointer, limit) + Overdrive.api.pointee.setLimit.unsafelyUnwrapped(pointer, limit) } public var limitModulator: SignalValue? { - get { SignalValue.wrap(Overdrive.api.getLimitModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(Overdrive.api.pointee.getLimitModulator.unsafelyUnwrapped(pointer)) } set { retain(newValue) - Overdrive.api.setLimitModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + Overdrive.api.pointee.setLimitModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// A DC offset applied to the input, making the clipping asymmetric. public func setOffset(_ offset: Float) { - Overdrive.api.setOffset.unsafelyUnwrapped(pointer, offset) + Overdrive.api.pointee.setOffset.unsafelyUnwrapped(pointer, offset) } public var offsetModulator: SignalValue? { - get { SignalValue.wrap(Overdrive.api.getOffsetModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(Overdrive.api.pointee.getOffsetModulator.unsafelyUnwrapped(pointer)) } set { retain(newValue) - Overdrive.api.setOffsetModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + Overdrive.api.pointee.setOffsetModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } diff --git a/Sources/PlayDate/SoundSignal.swift b/Sources/PlayDate/SoundSignal.swift index 890f843..feb4051 100644 --- a/Sources/PlayDate/SoundSignal.swift +++ b/Sources/PlayDate/SoundSignal.swift @@ -28,7 +28,7 @@ extension Sound { /// A signal object; also provides custom signals driven by Swift /// callbacks. Wraps `PDSynthSignal`. public final class Signal: SignalValue { - private static var api: playdate_sound_signal { snd.signal.pointee } + private static var api: UnsafePointer { snd.pointee.signal.unsafelyUnwrapped } /// Custom signal callbacks. public struct Callbacks { @@ -62,7 +62,7 @@ extension Sound { /// Creates a signal driven by the given callbacks. public init(callbacks: Callbacks) { let box = Unmanaged.passRetained(Box(callbacks)) - let pointer = Signal.api.newSignal.unsafelyUnwrapped( + let pointer = Signal.api.pointee.newSignal.unsafelyUnwrapped( { userdata, ioFrames, interpolationValue in guard let userdata else { return 0 } let box = Unmanaged.fromOpaque(userdata).takeUnretainedValue() @@ -89,7 +89,7 @@ extension Sound { /// Creates a plain signal object wrapping an existing signal value, /// so it can be scaled and offset. public init(value: SignalValue) { - let pointer = Signal.api.newSignalForValue.unsafelyUnwrapped(value.pointer) + let pointer = Signal.api.pointee.newSignalForValue.unsafelyUnwrapped(value.pointer) super.init(pointer: pointer.unsafelyUnwrapped, isOwned: true) } @@ -99,23 +99,23 @@ extension Sound { deinit { if isOwned { - Signal.api.freeSignal.unsafelyUnwrapped(pointer) + Signal.api.pointee.freeSignal.unsafelyUnwrapped(pointer) } } /// The signal's current value. public var value: Float { - Signal.api.getValue.unsafelyUnwrapped(pointer) + Signal.api.pointee.getValue.unsafelyUnwrapped(pointer) } /// Scales the signal's output. public func setValueScale(_ scale: Float) { - Signal.api.setValueScale.unsafelyUnwrapped(pointer, scale) + Signal.api.pointee.setValueScale.unsafelyUnwrapped(pointer, scale) } /// Offsets the signal's output. public func setValueOffset(_ offset: Float) { - Signal.api.setValueOffset.unsafelyUnwrapped(pointer, offset) + Signal.api.pointee.setValueOffset.unsafelyUnwrapped(pointer, offset) } } @@ -123,7 +123,7 @@ extension Sound { /// A low-frequency oscillator signal. Wraps `PDSynthLFO`. public final class LFO: SignalValue { - private static var api: playdate_sound_lfo { snd.lfo.pointee } + private static var api: UnsafePointer { snd.pointee.lfo.unsafelyUnwrapped } /// The oscillator's waveform. public enum Shape: UInt32, Sendable { @@ -142,43 +142,43 @@ extension Sound { var function: ((LFO) -> Float)? public init(shape: Shape = .sine) { - let pointer = LFO.api.newLFO.unsafelyUnwrapped(shape.cValue) + let pointer = LFO.api.pointee.newLFO.unsafelyUnwrapped(shape.cValue) super.init(pointer: pointer.unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - LFO.api.freeLFO.unsafelyUnwrapped(pointer) + LFO.api.pointee.freeLFO.unsafelyUnwrapped(pointer) } } public func setShape(_ shape: Shape) { - LFO.api.setType.unsafelyUnwrapped(pointer, shape.cValue) + LFO.api.pointee.setType.unsafelyUnwrapped(pointer, shape.cValue) } /// The LFO rate, in cycles per second. public func setRate(_ rate: Float) { - LFO.api.setRate.unsafelyUnwrapped(pointer, rate) + LFO.api.pointee.setRate.unsafelyUnwrapped(pointer, rate) } /// The current phase, 0...1. public func setPhase(_ phase: Float) { - LFO.api.setPhase.unsafelyUnwrapped(pointer, phase) + LFO.api.pointee.setPhase.unsafelyUnwrapped(pointer, phase) } /// The phase the LFO starts at when a note starts, 0...1. public func setStartPhase(_ phase: Float) { - LFO.api.setStartPhase.unsafelyUnwrapped(pointer, phase) + LFO.api.pointee.setStartPhase.unsafelyUnwrapped(pointer, phase) } /// The center value of the LFO output. public func setCenter(_ center: Float) { - LFO.api.setCenter.unsafelyUnwrapped(pointer, center) + LFO.api.pointee.setCenter.unsafelyUnwrapped(pointer, center) } /// The amplitude of the LFO around its center. public func setDepth(_ depth: Float) { - LFO.api.setDepth.unsafelyUnwrapped(pointer, depth) + LFO.api.pointee.setDepth.unsafelyUnwrapped(pointer, depth) } /// For `.arpeggiator` LFOs: the sequence of values (in half-steps) @@ -186,7 +186,7 @@ extension Sound { public func setArpeggiation(_ steps: [Float]) { var steps = steps steps.withUnsafeMutableBufferPointer { buffer in - LFO.api.setArpeggiation.unsafelyUnwrapped(pointer, Int32(buffer.count), + LFO.api.pointee.setArpeggiation.unsafelyUnwrapped(pointer, Int32(buffer.count), buffer.baseAddress) } } @@ -195,7 +195,7 @@ extension Sound { /// `interpolate` is `true`, values are interpolated between calls. public func setFunction(interpolate: Bool = false, _ function: @escaping (LFO) -> Float) { self.function = function - LFO.api.setFunction.unsafelyUnwrapped(pointer, { _, userdata in + LFO.api.pointee.setFunction.unsafelyUnwrapped(pointer, { _, userdata in guard let userdata else { return 0 } let lfo = Unmanaged.fromOpaque(userdata).takeUnretainedValue() return lfo.function?(lfo) ?? 0 @@ -205,26 +205,26 @@ extension Sound { /// Waits `holdoff` seconds after a note starts, then ramps the LFO /// depth up over `rampTime` seconds. public func setDelay(holdoff: Float, rampTime: Float) { - LFO.api.setDelay.unsafelyUnwrapped(pointer, holdoff, rampTime) + LFO.api.pointee.setDelay.unsafelyUnwrapped(pointer, holdoff, rampTime) } /// Whether the LFO phase restarts on every new note. public func setRetrigger(_ flag: Bool) { - LFO.api.setRetrigger.unsafelyUnwrapped(pointer, flag ? 1 : 0) + LFO.api.pointee.setRetrigger.unsafelyUnwrapped(pointer, flag ? 1 : 0) } /// When `true`, the LFO runs globally instead of per-note. public func setGlobal(_ global: Bool) { - LFO.api.setGlobal.unsafelyUnwrapped(pointer, global ? 1 : 0) + LFO.api.pointee.setGlobal.unsafelyUnwrapped(pointer, global ? 1 : 0) } /// Seeds the random number generator used by `.sampleAndHold` LFOs. public func setRandomSeed(_ seed: UInt16) { - LFO.api.setRandomSeed.unsafelyUnwrapped(pointer, seed) + LFO.api.pointee.setRandomSeed.unsafelyUnwrapped(pointer, seed) } public var value: Float { - LFO.api.getValue.unsafelyUnwrapped(pointer) + LFO.api.pointee.getValue.unsafelyUnwrapped(pointer) } } @@ -232,12 +232,12 @@ extension Sound { /// An ADSR envelope signal. Wraps `PDSynthEnvelope`. public final class Envelope: SignalValue { - private static var api: playdate_sound_envelope { snd.envelope.pointee } + private static var api: UnsafePointer { snd.pointee.envelope.unsafelyUnwrapped } /// Creates an envelope with the given attack and decay times /// (seconds), sustain level (0...1), and release time (seconds). public init(attack: Float = 0, decay: Float = 0, sustain: Float = 1, release: Float = 0) { - let pointer = Envelope.api.newEnvelope.unsafelyUnwrapped(attack, decay, sustain, release) + let pointer = Envelope.api.pointee.newEnvelope.unsafelyUnwrapped(attack, decay, sustain, release) super.init(pointer: pointer.unsafelyUnwrapped, isOwned: true) } @@ -247,56 +247,56 @@ extension Sound { deinit { if isOwned { - Envelope.api.freeEnvelope.unsafelyUnwrapped(pointer) + Envelope.api.pointee.freeEnvelope.unsafelyUnwrapped(pointer) } } public func setAttack(_ attack: Float) { - Envelope.api.setAttack.unsafelyUnwrapped(pointer, attack) + Envelope.api.pointee.setAttack.unsafelyUnwrapped(pointer, attack) } public func setDecay(_ decay: Float) { - Envelope.api.setDecay.unsafelyUnwrapped(pointer, decay) + Envelope.api.pointee.setDecay.unsafelyUnwrapped(pointer, decay) } public func setSustain(_ sustain: Float) { - Envelope.api.setSustain.unsafelyUnwrapped(pointer, sustain) + Envelope.api.pointee.setSustain.unsafelyUnwrapped(pointer, sustain) } public func setRelease(_ release: Float) { - Envelope.api.setRelease.unsafelyUnwrapped(pointer, release) + Envelope.api.pointee.setRelease.unsafelyUnwrapped(pointer, release) } /// When `true`, a new note while a note is playing does not restart /// the envelope. public func setLegato(_ flag: Bool) { - Envelope.api.setLegato.unsafelyUnwrapped(pointer, flag ? 1 : 0) + Envelope.api.pointee.setLegato.unsafelyUnwrapped(pointer, flag ? 1 : 0) } /// When `true`, a new note restarts the envelope from zero instead of /// its current value. public func setRetrigger(_ flag: Bool) { - Envelope.api.setRetrigger.unsafelyUnwrapped(pointer, flag ? 1 : 0) + Envelope.api.pointee.setRetrigger.unsafelyUnwrapped(pointer, flag ? 1 : 0) } /// Bends the envelope's segments: 0 is linear, 1 is maximum curvature. public func setCurvature(_ amount: Float) { - Envelope.api.setCurvature.unsafelyUnwrapped(pointer, amount) + Envelope.api.pointee.setCurvature.unsafelyUnwrapped(pointer, amount) } /// How much note velocity scales the envelope's output. public func setVelocitySensitivity(_ sensitivity: Float) { - Envelope.api.setVelocitySensitivity.unsafelyUnwrapped(pointer, sensitivity) + Envelope.api.pointee.setVelocitySensitivity.unsafelyUnwrapped(pointer, sensitivity) } /// Scales the envelope's rate by note: notes above `start` play the /// envelope faster (up to `scaling` at `end` and beyond). public func setRateScaling(_ scaling: Float, start: MIDINote, end: MIDINote) { - Envelope.api.setRateScaling.unsafelyUnwrapped(pointer, scaling, start, end) + Envelope.api.pointee.setRateScaling.unsafelyUnwrapped(pointer, scaling, start, end) } public var value: Float { - Envelope.api.getValue.unsafelyUnwrapped(pointer) + Envelope.api.pointee.getValue.unsafelyUnwrapped(pointer) } } @@ -305,10 +305,10 @@ extension Sound { /// A signal whose values are set on a sequence timeline. Wraps /// `ControlSignal`. public final class ControlSignal: SignalValue { - private static var api: playdate_control_signal { snd.controlsignal.pointee } + private static var api: UnsafePointer { snd.pointee.controlsignal.unsafelyUnwrapped } public init() { - let pointer = ControlSignal.api.newSignal.unsafelyUnwrapped() + let pointer = ControlSignal.api.pointee.newSignal.unsafelyUnwrapped() super.init(pointer: pointer.unsafelyUnwrapped, isOwned: true) } @@ -318,28 +318,28 @@ extension Sound { deinit { if isOwned { - ControlSignal.api.freeSignal.unsafelyUnwrapped(pointer) + ControlSignal.api.pointee.freeSignal.unsafelyUnwrapped(pointer) } } public func clearEvents() { - ControlSignal.api.clearEvents.unsafelyUnwrapped(pointer) + ControlSignal.api.pointee.clearEvents.unsafelyUnwrapped(pointer) } /// Adds a value at `step` in the signal's timeline. If `interpolate` /// is `true`, the value ramps from the previous event. public func addEvent(step: Int, value: Float, interpolate: Bool = false) { - ControlSignal.api.addEvent.unsafelyUnwrapped(pointer, Int32(step), value, + ControlSignal.api.pointee.addEvent.unsafelyUnwrapped(pointer, Int32(step), value, interpolate ? 1 : 0) } public func removeEvent(step: Int) { - ControlSignal.api.removeEvent.unsafelyUnwrapped(pointer, Int32(step)) + ControlSignal.api.pointee.removeEvent.unsafelyUnwrapped(pointer, Int32(step)) } /// The MIDI controller number for signals loaded from a MIDI file. public var midiControllerNumber: Int { - Int(ControlSignal.api.getMIDIControllerNumber.unsafelyUnwrapped(pointer)) + Int(ControlSignal.api.pointee.getMIDIControllerNumber.unsafelyUnwrapped(pointer)) } } } diff --git a/Sources/PlayDate/SoundSource.swift b/Sources/PlayDate/SoundSource.swift index 848f761..a55822c 100644 --- a/Sources/PlayDate/SoundSource.swift +++ b/Sources/PlayDate/SoundSource.swift @@ -9,7 +9,7 @@ extension Sound { /// A source of audio: the base class of `FilePlayer`, `SamplePlayer`, /// `Synth`, `DelayLineTap`, and `CallbackSource`. Wraps `SoundSource`. public class Source { - private static var api: playdate_sound_source { snd.source.pointee } + private static var api: UnsafePointer { snd.pointee.source.unsafelyUnwrapped } /// The underlying C object. Set once, immediately after creation. var pointer: OpaquePointer! @@ -23,7 +23,7 @@ extension Sound { /// Sets the playback volume for the left and right channels, 0...1. public func setVolume(left: Float, right: Float) { - Source.api.setVolume.unsafelyUnwrapped(pointer, left, right) + Source.api.pointee.setVolume.unsafelyUnwrapped(pointer, left, right) } /// Sets the playback volume of both channels. @@ -34,25 +34,25 @@ extension Sound { /// The playback volume of the left and right channels. public var volume: (left: Float, right: Float) { var left: Float = 0, right: Float = 0 - Source.api.getVolume.unsafelyUnwrapped(pointer, &left, &right) + Source.api.pointee.getVolume.unsafelyUnwrapped(pointer, &left, &right) return (left, right) } public var isPlaying: Bool { - Source.api.isPlaying.unsafelyUnwrapped(pointer) != 0 + Source.api.pointee.isPlaying.unsafelyUnwrapped(pointer) != 0 } /// Sets a function called when the source finishes playing. public func setFinishCallback(_ callback: ((Source) -> Void)?) { finishCallback = callback if callback != nil { - Source.api.setFinishCallback.unsafelyUnwrapped(pointer, { _, userdata in + Source.api.pointee.setFinishCallback.unsafelyUnwrapped(pointer, { _, userdata in guard let userdata else { return } let source = Unmanaged.fromOpaque(userdata).takeUnretainedValue() source.finishCallback?(source) }, Unmanaged.passUnretained(self).toOpaque()) } else { - Source.api.setFinishCallback.unsafelyUnwrapped(pointer, nil, nil) + Source.api.pointee.setFinishCallback.unsafelyUnwrapped(pointer, nil, nil) } } } @@ -106,7 +106,7 @@ extension Sound { /// Streams audio from a file. Wraps `FilePlayer`. public final class FilePlayer: Source { - private static var api: playdate_sound_fileplayer { snd.fileplayer.pointee } + private static var api: UnsafePointer { snd.pointee.fileplayer.unsafelyUnwrapped } var loopCallback: ((FilePlayer) -> Void)? var fadeCallback: ((FilePlayer) -> Void)? @@ -118,7 +118,7 @@ extension Sound { } public convenience init() { - self.init(pointer: FilePlayer.api.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped, + self.init(pointer: FilePlayer.api.pointee.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } @@ -130,14 +130,14 @@ extension Sound { deinit { if isOwned { - FilePlayer.api.freePlayer.unsafelyUnwrapped(pointer) + FilePlayer.api.pointee.freePlayer.unsafelyUnwrapped(pointer) } } /// Prepares the player to stream the file at `path`. public func load(path: String) throws(PlaydateError) { let loaded = path.withPlaydateCString { - FilePlayer.api.loadIntoPlayer.unsafelyUnwrapped(pointer, $0) != 0 + FilePlayer.api.pointee.loadIntoPlayer.unsafelyUnwrapped(pointer, $0) != 0 } if !loaded { throw PlaydateError(message: "unable to load audio file: \(path)") @@ -146,69 +146,69 @@ extension Sound { /// Sets the length of the stream buffer, in seconds. Default 0.25. public func setBufferLength(_ seconds: Float) { - FilePlayer.api.setBufferLength.unsafelyUnwrapped(pointer, seconds) + FilePlayer.api.pointee.setBufferLength.unsafelyUnwrapped(pointer, seconds) } /// Starts playback, looping `repeat` times; 0 loops endlessly. @discardableResult public func play(repeat repeatCount: Int = 1) -> Bool { - FilePlayer.api.play.unsafelyUnwrapped(pointer, Int32(repeatCount)) != 0 + FilePlayer.api.pointee.play.unsafelyUnwrapped(pointer, Int32(repeatCount)) != 0 } public func pause() { - FilePlayer.api.pause.unsafelyUnwrapped(pointer) + FilePlayer.api.pointee.pause.unsafelyUnwrapped(pointer) } public func stop() { - FilePlayer.api.stop.unsafelyUnwrapped(pointer) + FilePlayer.api.pointee.stop.unsafelyUnwrapped(pointer) } /// The file's length in seconds. public var length: Float { - FilePlayer.api.getLength.unsafelyUnwrapped(pointer) + FilePlayer.api.pointee.getLength.unsafelyUnwrapped(pointer) } /// The playback position in seconds. public var offset: Float { - get { FilePlayer.api.getOffset.unsafelyUnwrapped(pointer) } - set { FilePlayer.api.setOffset.unsafelyUnwrapped(pointer, newValue) } + get { FilePlayer.api.pointee.getOffset.unsafelyUnwrapped(pointer) } + set { FilePlayer.api.pointee.setOffset.unsafelyUnwrapped(pointer, newValue) } } /// The playback rate; 1 is normal speed, negative values are not /// supported. public var rate: Float { - get { FilePlayer.api.getRate.unsafelyUnwrapped(pointer) } - set { FilePlayer.api.setRate.unsafelyUnwrapped(pointer, newValue) } + get { FilePlayer.api.pointee.getRate.unsafelyUnwrapped(pointer) } + set { FilePlayer.api.pointee.setRate.unsafelyUnwrapped(pointer, newValue) } } /// Loops playback between `start` and `end` (seconds) while playing /// with `repeat` 0. An `end` of 0 means the end of the file. public func setLoopRange(start: Float, end: Float) { - FilePlayer.api.setLoopRange.unsafelyUnwrapped(pointer, start, end) + FilePlayer.api.pointee.setLoopRange.unsafelyUnwrapped(pointer, start, end) } /// Whether playback underran because the file could not be read fast /// enough. public var didUnderrun: Bool { - FilePlayer.api.didUnderrun.unsafelyUnwrapped(pointer) != 0 + FilePlayer.api.pointee.didUnderrun.unsafelyUnwrapped(pointer) != 0 } /// Stops playback (instead of looping the buffer) on underrun. public func setStopOnUnderrun(_ flag: Bool) { - FilePlayer.api.setStopOnUnderrun.unsafelyUnwrapped(pointer, flag ? 1 : 0) + FilePlayer.api.pointee.setStopOnUnderrun.unsafelyUnwrapped(pointer, flag ? 1 : 0) } /// Sets a function called every time playback loops. public func setLoopCallback(_ callback: ((FilePlayer) -> Void)?) { loopCallback = callback if callback != nil { - FilePlayer.api.setLoopCallback.unsafelyUnwrapped(pointer, { _, userdata in + FilePlayer.api.pointee.setLoopCallback.unsafelyUnwrapped(pointer, { _, userdata in guard let userdata else { return } let player = Unmanaged.fromOpaque(userdata).takeUnretainedValue() player.loopCallback?(player) }, Unmanaged.passUnretained(self).toOpaque()) } else { - FilePlayer.api.setLoopCallback.unsafelyUnwrapped(pointer, nil, nil) + FilePlayer.api.pointee.setLoopCallback.unsafelyUnwrapped(pointer, nil, nil) } } @@ -218,13 +218,13 @@ extension Sound { completion: ((FilePlayer) -> Void)? = nil) { fadeCallback = completion if completion != nil { - FilePlayer.api.fadeVolume.unsafelyUnwrapped(pointer, left, right, length, { _, userdata in + FilePlayer.api.pointee.fadeVolume.unsafelyUnwrapped(pointer, left, right, length, { _, userdata in guard let userdata else { return } let player = Unmanaged.fromOpaque(userdata).takeUnretainedValue() player.fadeCallback?(player) }, Unmanaged.passUnretained(self).toOpaque()) } else { - FilePlayer.api.fadeVolume.unsafelyUnwrapped(pointer, left, right, length, nil, nil) + FilePlayer.api.pointee.fadeVolume.unsafelyUnwrapped(pointer, left, right, length, nil, nil) } } @@ -234,7 +234,7 @@ extension Sound { public func setMP3StreamSource(bufferLength: Float, _ dataSource: @escaping (UnsafeMutableBufferPointer) -> Int) { mp3DataSource = dataSource - FilePlayer.api.setMP3StreamSource.unsafelyUnwrapped(pointer, { data, bytes, userdata in + FilePlayer.api.pointee.setMP3StreamSource.unsafelyUnwrapped(pointer, { data, bytes, userdata in guard let userdata, let data else { return 0 } let player = Unmanaged.fromOpaque(userdata).takeUnretainedValue() let buffer = UnsafeMutableBufferPointer(start: data, count: Int(bytes)) @@ -244,10 +244,10 @@ extension Sound { /// Modulates the playback rate. public var rateModulator: SignalValue? { - get { SignalValue.wrap(FilePlayer.api.getRateModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(FilePlayer.api.pointee.getRateModulator.unsafelyUnwrapped(pointer)) } set { retainedRateModulator = newValue - FilePlayer.api.setRateModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + FilePlayer.api.pointee.setRateModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } } @@ -256,7 +256,7 @@ extension Sound { /// Audio data loaded into memory. Wraps `AudioSample`. public final class AudioSample { - private static var api: playdate_sound_sample { snd.sample.pointee } + private static var api: UnsafePointer { snd.pointee.sample.unsafelyUnwrapped } let pointer: OpaquePointer let isOwned: Bool @@ -268,13 +268,13 @@ extension Sound { /// Allocates a sample buffer with room for `byteCount` bytes. public convenience init(byteCount: Int) { - self.init(pointer: AudioSample.api.newSampleBuffer.unsafelyUnwrapped( + self.init(pointer: AudioSample.api.pointee.newSampleBuffer.unsafelyUnwrapped( Int32(byteCount)).unsafelyUnwrapped, isOwned: true) } /// Loads the wav or aiff file at `path`. public convenience init(path: String) throws(PlaydateError) { - let pointer = path.withPlaydateCString { AudioSample.api.load.unsafelyUnwrapped($0) } + let pointer = path.withPlaydateCString { AudioSample.api.pointee.load.unsafelyUnwrapped($0) } guard let pointer else { throw PlaydateError(message: "unable to load sample: \(path)") } @@ -287,7 +287,7 @@ extension Sound { /// sample's lifetime. public convenience init?(data: UnsafeMutablePointer, format: Format, sampleRate: UInt32, byteCount: Int, freeWhenDone: Bool) { - guard let pointer = AudioSample.api.newSampleFromData.unsafelyUnwrapped( + guard let pointer = AudioSample.api.pointee.newSampleFromData.unsafelyUnwrapped( data, format.cValue, sampleRate, Int32(byteCount), freeWhenDone ? 1 : 0) else { return nil } @@ -296,14 +296,14 @@ extension Sound { deinit { if isOwned { - AudioSample.api.freeSample.unsafelyUnwrapped(pointer) + AudioSample.api.pointee.freeSample.unsafelyUnwrapped(pointer) } } /// Loads the file at `path` into this sample's buffer. public func load(path: String) throws(PlaydateError) { let loaded = path.withPlaydateCString { - AudioSample.api.loadIntoSample.unsafelyUnwrapped(pointer, $0) != 0 + AudioSample.api.pointee.loadIntoSample.unsafelyUnwrapped(pointer, $0) != 0 } if !loaded { throw PlaydateError(message: "unable to load sample: \(path)") @@ -316,20 +316,20 @@ extension Sound { var data: UnsafeMutablePointer? var format = kSound16bitMono var sampleRate: UInt32 = 0, byteLength: UInt32 = 0 - AudioSample.api.getData.unsafelyUnwrapped(pointer, &data, &format, &sampleRate, &byteLength) + AudioSample.api.pointee.getData.unsafelyUnwrapped(pointer, &data, &format, &sampleRate, &byteLength) return (data, Format(format), sampleRate, byteLength) } /// The sample's length in seconds. public var length: Float { - AudioSample.api.getLength.unsafelyUnwrapped(pointer) + AudioSample.api.pointee.getLength.unsafelyUnwrapped(pointer) } /// Decompresses an ADPCM sample to 16-bit PCM so it can be used in a /// synth. Returns `false` if there is not enough memory. @discardableResult public func decompress() -> Bool { - AudioSample.api.decompress.unsafelyUnwrapped(pointer) != 0 + AudioSample.api.pointee.decompress.unsafelyUnwrapped(pointer) != 0 } } @@ -337,7 +337,7 @@ extension Sound { /// Plays an `AudioSample` from memory. Wraps `SamplePlayer`. public final class SamplePlayer: Source { - private static var api: playdate_sound_sampleplayer { snd.sampleplayer.pointee } + private static var api: UnsafePointer { snd.pointee.sampleplayer.unsafelyUnwrapped } var loopCallback: ((SamplePlayer) -> Void)? private var retainedSample: AudioSample? @@ -348,7 +348,7 @@ extension Sound { } public convenience init() { - self.init(pointer: SamplePlayer.api.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped, + self.init(pointer: SamplePlayer.api.pointee.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } @@ -360,7 +360,7 @@ extension Sound { deinit { if isOwned { - SamplePlayer.api.freePlayer.unsafelyUnwrapped(pointer) + SamplePlayer.api.pointee.freePlayer.unsafelyUnwrapped(pointer) } } @@ -369,7 +369,7 @@ extension Sound { get { retainedSample } set { retainedSample = newValue - SamplePlayer.api.setSample.unsafelyUnwrapped(pointer, newValue?.pointer) + SamplePlayer.api.pointee.setSample.unsafelyUnwrapped(pointer, newValue?.pointer) } } @@ -377,59 +377,59 @@ extension Sound { /// endlessly, -1 loops ping-pong. @discardableResult public func play(repeat repeatCount: Int = 1, rate: Float = 1) -> Bool { - SamplePlayer.api.play.unsafelyUnwrapped(pointer, Int32(repeatCount), rate) != 0 + SamplePlayer.api.pointee.play.unsafelyUnwrapped(pointer, Int32(repeatCount), rate) != 0 } public func stop() { - SamplePlayer.api.stop.unsafelyUnwrapped(pointer) + SamplePlayer.api.pointee.stop.unsafelyUnwrapped(pointer) } public func setPaused(_ paused: Bool) { - SamplePlayer.api.setPaused.unsafelyUnwrapped(pointer, paused ? 1 : 0) + SamplePlayer.api.pointee.setPaused.unsafelyUnwrapped(pointer, paused ? 1 : 0) } /// The sample's length in seconds. public var length: Float { - SamplePlayer.api.getLength.unsafelyUnwrapped(pointer) + SamplePlayer.api.pointee.getLength.unsafelyUnwrapped(pointer) } /// The playback position in seconds. public var offset: Float { - get { SamplePlayer.api.getOffset.unsafelyUnwrapped(pointer) } - set { SamplePlayer.api.setOffset.unsafelyUnwrapped(pointer, newValue) } + get { SamplePlayer.api.pointee.getOffset.unsafelyUnwrapped(pointer) } + set { SamplePlayer.api.pointee.setOffset.unsafelyUnwrapped(pointer, newValue) } } /// The playback rate; 1 is normal speed, negative plays backward. public var rate: Float { - get { SamplePlayer.api.getRate.unsafelyUnwrapped(pointer) } - set { SamplePlayer.api.setRate.unsafelyUnwrapped(pointer, newValue) } + get { SamplePlayer.api.pointee.getRate.unsafelyUnwrapped(pointer) } + set { SamplePlayer.api.pointee.setRate.unsafelyUnwrapped(pointer, newValue) } } /// Restricts playback to the given range of sample frames. public func setPlayRange(start: Int, end: Int) { - SamplePlayer.api.setPlayRange.unsafelyUnwrapped(pointer, Int32(start), Int32(end)) + SamplePlayer.api.pointee.setPlayRange.unsafelyUnwrapped(pointer, Int32(start), Int32(end)) } /// Sets a function called every time playback loops. public func setLoopCallback(_ callback: ((SamplePlayer) -> Void)?) { loopCallback = callback if callback != nil { - SamplePlayer.api.setLoopCallback.unsafelyUnwrapped(pointer, { _, userdata in + SamplePlayer.api.pointee.setLoopCallback.unsafelyUnwrapped(pointer, { _, userdata in guard let userdata else { return } let player = Unmanaged.fromOpaque(userdata).takeUnretainedValue() player.loopCallback?(player) }, Unmanaged.passUnretained(self).toOpaque()) } else { - SamplePlayer.api.setLoopCallback.unsafelyUnwrapped(pointer, nil, nil) + SamplePlayer.api.pointee.setLoopCallback.unsafelyUnwrapped(pointer, nil, nil) } } /// Modulates the playback rate. public var rateModulator: SignalValue? { - get { SignalValue.wrap(SamplePlayer.api.getRateModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(SamplePlayer.api.pointee.getRateModulator.unsafelyUnwrapped(pointer)) } set { retainedRateModulator = newValue - SamplePlayer.api.setRateModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + SamplePlayer.api.pointee.setRateModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } } diff --git a/Sources/PlayDate/SoundSynth.swift b/Sources/PlayDate/SoundSynth.swift index 28f9c01..0924734 100644 --- a/Sources/PlayDate/SoundSynth.swift +++ b/Sources/PlayDate/SoundSynth.swift @@ -8,7 +8,7 @@ internal import CPlaydate extension Sound { /// A synthesizer voice. Wraps `PDSynth`. public final class Synth: Source { - private static var api: playdate_sound_synth { snd.synth.pointee } + private static var api: UnsafePointer { snd.pointee.synth.unsafelyUnwrapped } /// The synth's waveform. public enum Waveform: UInt32, Sendable { @@ -72,7 +72,7 @@ extension Sound { } public convenience init() { - self.init(pointer: Synth.api.newSynth.unsafelyUnwrapped().unsafelyUnwrapped, + self.init(pointer: Synth.api.pointee.newSynth.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } @@ -83,27 +83,27 @@ extension Sound { deinit { if isOwned { - Synth.api.freeSynth.unsafelyUnwrapped(pointer) + Synth.api.pointee.freeSynth.unsafelyUnwrapped(pointer) } } /// Copies the synth (and its generator, if any). public func copy() -> Synth { - Synth(pointer: Synth.api.copy.unsafelyUnwrapped(pointer).unsafelyUnwrapped, + Synth(pointer: Synth.api.pointee.copy.unsafelyUnwrapped(pointer).unsafelyUnwrapped, isOwned: true) } // MARK: Sound generation public func setWaveform(_ waveform: Waveform) { - Synth.api.setWaveform.unsafelyUnwrapped(pointer, waveform.cValue) + Synth.api.pointee.setWaveform.unsafelyUnwrapped(pointer, waveform.cValue) } /// Plays a sample instead of a waveform. A nonzero sustain range /// loops that part of the sample while the note is held. public func setSample(_ sample: AudioSample, sustainStart: UInt32 = 0, sustainEnd: UInt32 = 0) { retainedSample = sample - Synth.api.setSample.unsafelyUnwrapped(pointer, sample.pointer, sustainStart, sustainEnd) + Synth.api.pointee.setSample.unsafelyUnwrapped(pointer, sample.pointer, sustainStart, sustainEnd) } /// Uses a wavetable for the synth. `log2size` is the base-2 log of @@ -111,7 +111,7 @@ extension Sound { public func setWavetable(_ sample: AudioSample, log2size: Int, columns: Int, rows: Int) throws(PlaydateError) { retainedSample = sample - guard Synth.api.setWavetable.unsafelyUnwrapped( + guard Synth.api.pointee.setWavetable.unsafelyUnwrapped( pointer, sample.pointer, Int32(log2size), Int32(columns), Int32(rows)) != 0 else { throw PlaydateError(message: "invalid wavetable dimensions") } @@ -120,7 +120,7 @@ extension Sound { /// Provides audio via custom Swift callbacks. public func setGenerator(stereo: Bool, _ generator: Generator) { let box = Unmanaged.passRetained(GeneratorBox(generator, stereo: stereo)) - Synth.api.setGenerator.unsafelyUnwrapped( + Synth.api.pointee.setGenerator.unsafelyUnwrapped( pointer, stereo ? 1 : 0, { userdata, left, right, nsamples, rate, drate in guard let userdata, let left else { return 0 } @@ -159,75 +159,75 @@ extension Sound { // MARK: Envelope public func setAttackTime(_ attack: Float) { - Synth.api.setAttackTime.unsafelyUnwrapped(pointer, attack) + Synth.api.pointee.setAttackTime.unsafelyUnwrapped(pointer, attack) } public func setDecayTime(_ decay: Float) { - Synth.api.setDecayTime.unsafelyUnwrapped(pointer, decay) + Synth.api.pointee.setDecayTime.unsafelyUnwrapped(pointer, decay) } public func setSustainLevel(_ sustain: Float) { - Synth.api.setSustainLevel.unsafelyUnwrapped(pointer, sustain) + Synth.api.pointee.setSustainLevel.unsafelyUnwrapped(pointer, sustain) } public func setReleaseTime(_ release: Float) { - Synth.api.setReleaseTime.unsafelyUnwrapped(pointer, release) + Synth.api.pointee.setReleaseTime.unsafelyUnwrapped(pointer, release) } /// The synth's amplitude envelope. Owned by the synth. public var envelope: Envelope? { - guard let envelope = Synth.api.getEnvelope.unsafelyUnwrapped(pointer) else { return nil } + guard let envelope = Synth.api.pointee.getEnvelope.unsafelyUnwrapped(pointer) else { return nil } return Envelope(pointer: envelope, isOwned: false) } /// Clears the synth's envelope so it plays at constant volume. public func clearEnvelope() { - Synth.api.clearEnvelope.unsafelyUnwrapped(pointer) + Synth.api.pointee.clearEnvelope.unsafelyUnwrapped(pointer) } // MARK: Modulation /// Transposes played notes by `halfSteps` (fractional values allowed). public func setTranspose(_ halfSteps: Float) { - Synth.api.setTranspose.unsafelyUnwrapped(pointer, halfSteps) + Synth.api.pointee.setTranspose.unsafelyUnwrapped(pointer, halfSteps) } public var frequencyModulator: SignalValue? { - get { SignalValue.wrap(Synth.api.getFrequencyModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(Synth.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) } set { retain(newValue) - Synth.api.setFrequencyModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + Synth.api.pointee.setFrequencyModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } public var amplitudeModulator: SignalValue? { - get { SignalValue.wrap(Synth.api.getAmplitudeModulator.unsafelyUnwrapped(pointer)) } + get { SignalValue.wrap(Synth.api.pointee.getAmplitudeModulator.unsafelyUnwrapped(pointer)) } set { retain(newValue) - Synth.api.setAmplitudeModulator.unsafelyUnwrapped(pointer, newValue?.pointer) + Synth.api.pointee.setAmplitudeModulator.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// The number of parameters the synth's generator supports. public var parameterCount: Int { - Int(Synth.api.getParameterCount.unsafelyUnwrapped(pointer)) + Int(Synth.api.pointee.getParameterCount.unsafelyUnwrapped(pointer)) } /// Sets a generator parameter. Returns `false` if the parameter is /// invalid. @discardableResult public func setParameter(_ parameter: Int, value: Float) -> Bool { - Synth.api.setParameter.unsafelyUnwrapped(pointer, Int32(parameter), value) != 0 + Synth.api.pointee.setParameter.unsafelyUnwrapped(pointer, Int32(parameter), value) != 0 } public func setParameterModulator(_ parameter: Int, _ modulator: SignalValue?) { retain(modulator) - Synth.api.setParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter), + Synth.api.pointee.setParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter), modulator?.pointer) } public func parameterModulator(_ parameter: Int) -> SignalValue? { - SignalValue.wrap(Synth.api.getParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter))) + SignalValue.wrap(Synth.api.pointee.getParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter))) } private func retain(_ modulator: SignalValue?) { @@ -243,23 +243,23 @@ extension Sound { /// or 0 for immediately. public func playNote(frequency: Float, velocity: Float = 1, length: Float? = nil, when: UInt32 = 0) { - Synth.api.playNote.unsafelyUnwrapped(pointer, frequency, velocity, length ?? -1, when) + Synth.api.pointee.playNote.unsafelyUnwrapped(pointer, frequency, velocity, length ?? -1, when) } /// Plays a MIDI note, where 60 is middle C. public func playMIDINote(_ note: MIDINote, velocity: Float = 1, length: Float? = nil, when: UInt32 = 0) { - Synth.api.playMIDINote.unsafelyUnwrapped(pointer, note, velocity, length ?? -1, when) + Synth.api.pointee.playMIDINote.unsafelyUnwrapped(pointer, note, velocity, length ?? -1, when) } /// Releases the playing note at time `when`, or immediately if 0. public func noteOff(when: UInt32 = 0) { - Synth.api.noteOff.unsafelyUnwrapped(pointer, when) + Synth.api.pointee.noteOff.unsafelyUnwrapped(pointer, when) } /// Stops the synth immediately, without playing the release phase. public func stop() { - Synth.api.stop.unsafelyUnwrapped(pointer) + Synth.api.pointee.stop.unsafelyUnwrapped(pointer) } } @@ -268,7 +268,7 @@ extension Sound { /// A bank of synth voices for playing a sequence track. Wraps /// `PDSynthInstrument`. public final class Instrument { - private static var api: playdate_sound_instrument { snd.instrument.pointee } + private static var api: UnsafePointer { snd.pointee.instrument.unsafelyUnwrapped } let pointer: OpaquePointer let isOwned: Bool @@ -280,13 +280,13 @@ extension Sound { } public convenience init() { - self.init(pointer: Instrument.api.newInstrument.unsafelyUnwrapped().unsafelyUnwrapped, + self.init(pointer: Instrument.api.pointee.newInstrument.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - Instrument.api.freeInstrument.unsafelyUnwrapped(pointer) + Instrument.api.pointee.freeInstrument.unsafelyUnwrapped(pointer) } } @@ -296,7 +296,7 @@ extension Sound { @discardableResult public func addVoice(_ synth: Synth, rangeStart: MIDINote = 0, rangeEnd: MIDINote = 127, transpose: Float = 0) -> Bool { - let added = Instrument.api.addVoice.unsafelyUnwrapped( + let added = Instrument.api.pointee.addVoice.unsafelyUnwrapped( pointer, synth.pointer, rangeStart, rangeEnd, transpose) != 0 if added, !retainedVoices.contains(where: { $0 === synth }) { retainedVoices.append(synth) @@ -309,7 +309,7 @@ extension Sound { @discardableResult public func playNote(frequency: Float, velocity: Float = 1, length: Float? = nil, when: UInt32 = 0) -> Synth? { - let synth = Instrument.api.playNote.unsafelyUnwrapped( + let synth = Instrument.api.pointee.playNote.unsafelyUnwrapped( pointer, frequency, velocity, length ?? -1, when) return voice(for: synth) } @@ -318,7 +318,7 @@ extension Sound { @discardableResult public func playMIDINote(_ note: MIDINote, velocity: Float = 1, length: Float? = nil, when: UInt32 = 0) -> Synth? { - let synth = Instrument.api.playMIDINote.unsafelyUnwrapped( + let synth = Instrument.api.pointee.playMIDINote.unsafelyUnwrapped( pointer, note, velocity, length ?? -1, when) return voice(for: synth) } @@ -333,38 +333,38 @@ extension Sound { /// Bends played notes by `bend` × the pitch bend range. public func setPitchBend(_ bend: Float) { - Instrument.api.setPitchBend.unsafelyUnwrapped(pointer, bend) + Instrument.api.pointee.setPitchBend.unsafelyUnwrapped(pointer, bend) } public func setPitchBendRange(halfSteps: Float) { - Instrument.api.setPitchBendRange.unsafelyUnwrapped(pointer, halfSteps) + Instrument.api.pointee.setPitchBendRange.unsafelyUnwrapped(pointer, halfSteps) } public func setTranspose(halfSteps: Float) { - Instrument.api.setTranspose.unsafelyUnwrapped(pointer, halfSteps) + Instrument.api.pointee.setTranspose.unsafelyUnwrapped(pointer, halfSteps) } /// Releases the voice playing `note` at time `when` (0 = now). public func noteOff(_ note: MIDINote, when: UInt32 = 0) { - Instrument.api.noteOff.unsafelyUnwrapped(pointer, note, when) + Instrument.api.pointee.noteOff.unsafelyUnwrapped(pointer, note, when) } public func allNotesOff(when: UInt32 = 0) { - Instrument.api.allNotesOff.unsafelyUnwrapped(pointer, when) + Instrument.api.pointee.allNotesOff.unsafelyUnwrapped(pointer, when) } public func setVolume(left: Float, right: Float) { - Instrument.api.setVolume.unsafelyUnwrapped(pointer, left, right) + Instrument.api.pointee.setVolume.unsafelyUnwrapped(pointer, left, right) } public var volume: (left: Float, right: Float) { var left: Float = 0, right: Float = 0 - Instrument.api.getVolume.unsafelyUnwrapped(pointer, &left, &right) + Instrument.api.pointee.getVolume.unsafelyUnwrapped(pointer, &left, &right) return (left, right) } public var activeVoiceCount: Int { - Int(Instrument.api.activeVoiceCount.unsafelyUnwrapped(pointer)) + Int(Instrument.api.pointee.activeVoiceCount.unsafelyUnwrapped(pointer)) } } @@ -372,7 +372,7 @@ extension Sound { /// A track of notes played by an instrument. Wraps `SequenceTrack`. public final class SequenceTrack { - private static var api: playdate_sound_track { snd.track.pointee } + private static var api: UnsafePointer { snd.pointee.track.unsafelyUnwrapped } let pointer: OpaquePointer let isOwned: Bool @@ -384,13 +384,13 @@ extension Sound { } public convenience init() { - self.init(pointer: SequenceTrack.api.newTrack.unsafelyUnwrapped().unsafelyUnwrapped, + self.init(pointer: SequenceTrack.api.pointee.newTrack.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - SequenceTrack.api.freeTrack.unsafelyUnwrapped(pointer) + SequenceTrack.api.pointee.freeTrack.unsafelyUnwrapped(pointer) } } @@ -398,38 +398,38 @@ extension Sound { public var instrument: Instrument? { get { if let retainedInstrument { return retainedInstrument } - guard let instrument = SequenceTrack.api.getInstrument.unsafelyUnwrapped(pointer) else { + guard let instrument = SequenceTrack.api.pointee.getInstrument.unsafelyUnwrapped(pointer) else { return nil } return Instrument(pointer: instrument, isOwned: false) } set { retainedInstrument = newValue - SequenceTrack.api.setInstrument.unsafelyUnwrapped(pointer, newValue?.pointer) + SequenceTrack.api.pointee.setInstrument.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// Adds a note starting at `step`, lasting `length` steps. public func addNote(step: UInt32, length: UInt32, note: MIDINote, velocity: Float = 1) { - SequenceTrack.api.addNoteEvent.unsafelyUnwrapped(pointer, step, length, note, velocity) + SequenceTrack.api.pointee.addNoteEvent.unsafelyUnwrapped(pointer, step, length, note, velocity) } public func removeNote(step: UInt32, note: MIDINote) { - SequenceTrack.api.removeNoteEvent.unsafelyUnwrapped(pointer, step, note) + SequenceTrack.api.pointee.removeNoteEvent.unsafelyUnwrapped(pointer, step, note) } public func clearNotes() { - SequenceTrack.api.clearNotes.unsafelyUnwrapped(pointer) + SequenceTrack.api.pointee.clearNotes.unsafelyUnwrapped(pointer) } /// The track's length in steps, including the tail of the last note. public var length: UInt32 { - SequenceTrack.api.getLength.unsafelyUnwrapped(pointer) + SequenceTrack.api.pointee.getLength.unsafelyUnwrapped(pointer) } /// The index of the first note at or after `step`. public func indexForStep(_ step: UInt32) -> Int { - Int(SequenceTrack.api.getIndexForStep.unsafelyUnwrapped(pointer, step)) + Int(SequenceTrack.api.pointee.getIndexForStep.unsafelyUnwrapped(pointer, step)) } /// The note at `index`, or `nil` if the index is out of range. @@ -438,19 +438,19 @@ extension Sound { var step: UInt32 = 0, length: UInt32 = 0 var note: MIDINote = 0 var velocity: Float = 0 - guard SequenceTrack.api.getNoteAtIndex.unsafelyUnwrapped( + guard SequenceTrack.api.pointee.getNoteAtIndex.unsafelyUnwrapped( pointer, Int32(index), &step, &length, ¬e, &velocity) != 0 else { return nil } return (step, length, note, velocity) } /// The number of control signals on the track. public var controlSignalCount: Int { - Int(SequenceTrack.api.getControlSignalCount.unsafelyUnwrapped(pointer)) + Int(SequenceTrack.api.pointee.getControlSignalCount.unsafelyUnwrapped(pointer)) } /// The control signal at `index`. Owned by the track. public func controlSignal(at index: Int) -> ControlSignal? { - guard let signal = SequenceTrack.api.getControlSignal.unsafelyUnwrapped( + guard let signal = SequenceTrack.api.pointee.getControlSignal.unsafelyUnwrapped( pointer, Int32(index)) else { return nil } return ControlSignal(pointer: signal, isOwned: false) } @@ -458,26 +458,26 @@ extension Sound { /// The control signal for MIDI controller `controller`, optionally /// creating it. Owned by the track. public func signalForController(_ controller: Int, create: Bool = false) -> ControlSignal? { - guard let signal = SequenceTrack.api.getSignalForController.unsafelyUnwrapped( + guard let signal = SequenceTrack.api.pointee.getSignalForController.unsafelyUnwrapped( pointer, Int32(controller), create ? 1 : 0) else { return nil } return ControlSignal(pointer: signal, isOwned: false) } public func clearControlEvents() { - SequenceTrack.api.clearControlEvents.unsafelyUnwrapped(pointer) + SequenceTrack.api.pointee.clearControlEvents.unsafelyUnwrapped(pointer) } /// The maximum number of simultaneous notes in the track. public var polyphony: Int { - Int(SequenceTrack.api.getPolyphony.unsafelyUnwrapped(pointer)) + Int(SequenceTrack.api.pointee.getPolyphony.unsafelyUnwrapped(pointer)) } public var activeVoiceCount: Int { - Int(SequenceTrack.api.activeVoiceCount.unsafelyUnwrapped(pointer)) + Int(SequenceTrack.api.pointee.activeVoiceCount.unsafelyUnwrapped(pointer)) } public func setMuted(_ muted: Bool) { - SequenceTrack.api.setMuted.unsafelyUnwrapped(pointer, muted ? 1 : 0) + SequenceTrack.api.pointee.setMuted.unsafelyUnwrapped(pointer, muted ? 1 : 0) } } @@ -486,14 +486,14 @@ extension Sound { /// A collection of tracks with tempo and loop control, playable from a /// MIDI file. Wraps `SoundSequence`. public final class Sequence { - private static var api: playdate_sound_sequence { snd.sequence.pointee } + private static var api: UnsafePointer { snd.pointee.sequence.unsafelyUnwrapped } let pointer: OpaquePointer private var retainedTracks: [SequenceTrack] = [] var finishCallback: ((Sequence) -> Void)? public init() { - pointer = Sequence.api.newSequence.unsafelyUnwrapped().unsafelyUnwrapped + pointer = Sequence.api.pointee.newSequence.unsafelyUnwrapped().unsafelyUnwrapped } /// Creates a sequence and loads the MIDI file at `path`. @@ -503,12 +503,12 @@ extension Sound { } deinit { - Sequence.api.freeSequence.unsafelyUnwrapped(pointer) + Sequence.api.pointee.freeSequence.unsafelyUnwrapped(pointer) } public func loadMIDIFile(path: String) throws(PlaydateError) { let loaded = path.withPlaydateCString { - Sequence.api.loadMIDIFile.unsafelyUnwrapped(pointer, $0) != 0 + Sequence.api.pointee.loadMIDIFile.unsafelyUnwrapped(pointer, $0) != 0 } if !loaded { throw PlaydateError(message: "unable to load MIDI file: \(path)") @@ -519,65 +519,65 @@ extension Sound { public func play(completion: ((Sequence) -> Void)? = nil) { finishCallback = completion if completion != nil { - Sequence.api.play.unsafelyUnwrapped(pointer, { _, userdata in + Sequence.api.pointee.play.unsafelyUnwrapped(pointer, { _, userdata in guard let userdata else { return } let sequence = Unmanaged.fromOpaque(userdata).takeUnretainedValue() sequence.finishCallback?(sequence) }, Unmanaged.passUnretained(self).toOpaque()) } else { - Sequence.api.play.unsafelyUnwrapped(pointer, nil, nil) + Sequence.api.pointee.play.unsafelyUnwrapped(pointer, nil, nil) } } public func stop() { - Sequence.api.stop.unsafelyUnwrapped(pointer) + Sequence.api.pointee.stop.unsafelyUnwrapped(pointer) } public var isPlaying: Bool { - Sequence.api.isPlaying.unsafelyUnwrapped(pointer) != 0 + Sequence.api.pointee.isPlaying.unsafelyUnwrapped(pointer) != 0 } /// The playback position, in samples. public var time: UInt32 { - get { Sequence.api.getTime.unsafelyUnwrapped(pointer) } - set { Sequence.api.setTime.unsafelyUnwrapped(pointer, newValue) } + get { Sequence.api.pointee.getTime.unsafelyUnwrapped(pointer) } + set { Sequence.api.pointee.setTime.unsafelyUnwrapped(pointer, newValue) } } /// The tempo, in steps per second. public var tempo: Float { - get { Sequence.api.getTempo.unsafelyUnwrapped(pointer) } - set { Sequence.api.setTempo.unsafelyUnwrapped(pointer, newValue) } + get { Sequence.api.pointee.getTempo.unsafelyUnwrapped(pointer) } + set { Sequence.api.pointee.setTempo.unsafelyUnwrapped(pointer, newValue) } } /// The sequence's length in steps, including the tail of the last note. public var length: UInt32 { - Sequence.api.getLength.unsafelyUnwrapped(pointer) + Sequence.api.pointee.getLength.unsafelyUnwrapped(pointer) } /// Loops the range `loopStart.. SequenceTrack { let track = SequenceTrack( - pointer: Sequence.api.addTrack.unsafelyUnwrapped(pointer).unsafelyUnwrapped, + pointer: Sequence.api.pointee.addTrack.unsafelyUnwrapped(pointer).unsafelyUnwrapped, isOwned: false) retainedTracks.append(track) return track @@ -593,7 +593,7 @@ extension Sound { /// The track at `index`. Owned by the sequence. public func track(at index: Int) -> SequenceTrack? { - guard let track = Sequence.api.getTrackAtIndex.unsafelyUnwrapped( + guard let track = Sequence.api.pointee.getTrackAtIndex.unsafelyUnwrapped( pointer, UInt32(index)) else { return nil } return SequenceTrack(pointer: track, isOwned: false) } @@ -603,12 +603,12 @@ extension Sound { if !retainedTracks.contains(where: { $0 === track }) { retainedTracks.append(track) } - Sequence.api.setTrackAtIndex.unsafelyUnwrapped(pointer, track.pointer, UInt32(index)) + Sequence.api.pointee.setTrackAtIndex.unsafelyUnwrapped(pointer, track.pointer, UInt32(index)) } /// Releases every playing note in the sequence. public func allNotesOff() { - Sequence.api.allNotesOff.unsafelyUnwrapped(pointer) + Sequence.api.pointee.allNotesOff.unsafelyUnwrapped(pointer) } } } diff --git a/Sources/PlayDate/Sprite.swift b/Sources/PlayDate/Sprite.swift index 7a67488..1b8d206 100644 --- a/Sources/PlayDate/Sprite.swift +++ b/Sources/PlayDate/Sprite.swift @@ -10,7 +10,7 @@ internal import CPlaydate -private var spriteAPI: playdate_sprite { Playdate.api.sprite.pointee } +private var spriteAPI: UnsafePointer { Playdate.spriteAPI } /// A floating-point rectangle mirroring `PDRect`. public struct Rect: Sendable { @@ -55,25 +55,25 @@ public final class Sprite { init(pointer: OpaquePointer, isOwned: Bool) { self.pointer = pointer self.isOwned = isOwned - spriteAPI.setUserdata.unsafelyUnwrapped(pointer, Unmanaged.passUnretained(self).toOpaque()) + spriteAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, Unmanaged.passUnretained(self).toOpaque()) } /// Allocates a new sprite. public convenience init() { - self.init(pointer: spriteAPI.newSprite.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) + self.init(pointer: spriteAPI.pointee.newSprite.unsafelyUnwrapped().unsafelyUnwrapped, isOwned: true) } deinit { if isOwned { - spriteAPI.setUserdata.unsafelyUnwrapped(pointer, nil) - spriteAPI.freeSprite.unsafelyUnwrapped(pointer) + spriteAPI.pointee.setUserdata.unsafelyUnwrapped(pointer, nil) + spriteAPI.pointee.freeSprite.unsafelyUnwrapped(pointer) } } /// Returns the Swift wrapper stored in the sprite's userdata, or a /// transient unowned wrapper for sprites created outside the binding. static func wrapper(for pointer: OpaquePointer) -> Sprite { - if let userdata = spriteAPI.getUserdata.unsafelyUnwrapped(pointer) { + if let userdata = spriteAPI.pointee.getUserdata.unsafelyUnwrapped(pointer) { return Unmanaged.fromOpaque(userdata).takeUnretainedValue() } return Sprite(pointer: pointer, isOwned: false) @@ -82,7 +82,7 @@ public final class Sprite { /// Copies the sprite. Callbacks and retained resources are carried /// over to the copy. public func copy() -> Sprite { - let copy = Sprite(pointer: spriteAPI.copy.unsafelyUnwrapped(pointer).unsafelyUnwrapped, + let copy = Sprite(pointer: spriteAPI.pointee.copy.unsafelyUnwrapped(pointer).unsafelyUnwrapped, isOwned: true) copy.updateFunction = updateFunction copy.drawFunction = drawFunction @@ -174,32 +174,32 @@ public final class Sprite { /// When `true`, all sprites redraw every frame instead of only when /// marked dirty. public static func setAlwaysRedraw(_ flag: Bool) { - spriteAPI.setAlwaysRedraw.unsafelyUnwrapped(flag ? 1 : 0) + spriteAPI.pointee.setAlwaysRedraw.unsafelyUnwrapped(flag ? 1 : 0) } /// Marks the given screen region as needing a redraw. public static func addDirtyRect(_ rect: Graphics.Rect) { - spriteAPI.addDirtyRect.unsafelyUnwrapped(rect.cValue) + spriteAPI.pointee.addDirtyRect.unsafelyUnwrapped(rect.cValue) } /// Draws every sprite in the display list. public static func drawAll() { - spriteAPI.drawSprites.unsafelyUnwrapped() + spriteAPI.pointee.drawSprites.unsafelyUnwrapped() } /// Updates and then draws every sprite in the display list. public static func updateAndDrawAll() { - spriteAPI.updateAndDrawSprites.unsafelyUnwrapped() + spriteAPI.pointee.updateAndDrawSprites.unsafelyUnwrapped() } /// The number of sprites in the display list. public static var count: Int { - Int(spriteAPI.getSpriteCount.unsafelyUnwrapped()) + Int(spriteAPI.pointee.getSpriteCount.unsafelyUnwrapped()) } /// Adds the sprite to the display list. public func add() { - spriteAPI.addSprite.unsafelyUnwrapped(pointer) + spriteAPI.pointee.addSprite.unsafelyUnwrapped(pointer) if !Sprite.displayList.contains(where: { $0 === self }) { Sprite.displayList.append(self) } @@ -207,7 +207,7 @@ public final class Sprite { /// Removes the sprite from the display list. public func remove() { - spriteAPI.removeSprite.unsafelyUnwrapped(pointer) + spriteAPI.pointee.removeSprite.unsafelyUnwrapped(pointer) Sprite.displayList.removeAll { $0 === self } } @@ -218,7 +218,7 @@ public final class Sprite { /// Removes every sprite from the display list. public static func removeAll() { - spriteAPI.removeAllSprites.unsafelyUnwrapped() + spriteAPI.pointee.removeAllSprites.unsafelyUnwrapped() displayList = [] } @@ -226,30 +226,30 @@ public final class Sprite { /// The sprite's bounds. Setting this positions and sizes the sprite. public var bounds: Rect { - get { Rect(spriteAPI.getBounds.unsafelyUnwrapped(pointer)) } - set { spriteAPI.setBounds.unsafelyUnwrapped(pointer, newValue.cValue) } + get { Rect(spriteAPI.pointee.getBounds.unsafelyUnwrapped(pointer)) } + set { spriteAPI.pointee.setBounds.unsafelyUnwrapped(pointer, newValue.cValue) } } /// Moves the sprite so its anchor point is at (x, y). public func moveTo(x: Float, y: Float) { - spriteAPI.moveTo.unsafelyUnwrapped(pointer, x, y) + spriteAPI.pointee.moveTo.unsafelyUnwrapped(pointer, x, y) } /// Moves the sprite by (dx, dy). public func moveBy(dx: Float, dy: Float) { - spriteAPI.moveBy.unsafelyUnwrapped(pointer, dx, dy) + spriteAPI.pointee.moveBy.unsafelyUnwrapped(pointer, dx, dy) } /// The sprite's anchor position. public var position: (x: Float, y: Float) { var x: Float = 0, y: Float = 0 - spriteAPI.getPosition.unsafelyUnwrapped(pointer, &x, &y) + spriteAPI.pointee.getPosition.unsafelyUnwrapped(pointer, &x, &y) return (x, y) } /// Sets the sprite's size without changing its image. public func setSize(width: Float, height: Float) { - spriteAPI.setSize.unsafelyUnwrapped(pointer, width, height) + spriteAPI.pointee.setSize.unsafelyUnwrapped(pointer, width, height) } /// The anchor point used for positioning, where (0, 0) is the top @@ -257,16 +257,16 @@ public final class Sprite { public var center: (x: Float, y: Float) { get { var x: Float = 0, y: Float = 0 - spriteAPI.getCenter.unsafelyUnwrapped(pointer, &x, &y) + spriteAPI.pointee.getCenter.unsafelyUnwrapped(pointer, &x, &y) return (x, y) } - set { spriteAPI.setCenter.unsafelyUnwrapped(pointer, newValue.x, newValue.y) } + set { spriteAPI.pointee.setCenter.unsafelyUnwrapped(pointer, newValue.x, newValue.y) } } /// Draw order: higher values draw on top. public var zIndex: Int16 { - get { spriteAPI.getZIndex.unsafelyUnwrapped(pointer) } - set { spriteAPI.setZIndex.unsafelyUnwrapped(pointer, newValue) } + get { spriteAPI.pointee.getZIndex.unsafelyUnwrapped(pointer) } + set { spriteAPI.pointee.setZIndex.unsafelyUnwrapped(pointer, newValue) } } // MARK: - Appearance @@ -274,13 +274,13 @@ public final class Sprite { /// Sets the sprite's image, resizing its bounds to match. public func setImage(_ image: Graphics.Bitmap?, flip: Graphics.BitmapFlip = .unflipped) { retainedImage = image - spriteAPI.setImage.unsafelyUnwrapped(pointer, image?.pointer, flip.cValue) + spriteAPI.pointee.setImage.unsafelyUnwrapped(pointer, image?.pointer, flip.cValue) } /// The sprite's image. public var image: Graphics.Bitmap? { if let retainedImage { return retainedImage } - guard let image = spriteAPI.getImage.unsafelyUnwrapped(pointer) else { return nil } + guard let image = spriteAPI.pointee.getImage.unsafelyUnwrapped(pointer) else { return nil } return Graphics.Bitmap(pointer: image, isOwned: false) } @@ -289,104 +289,104 @@ public final class Sprite { get { retainedTilemap } set { retainedTilemap = newValue - spriteAPI.setTilemap.unsafelyUnwrapped(pointer, newValue?.pointer) + spriteAPI.pointee.setTilemap.unsafelyUnwrapped(pointer, newValue?.pointer) } } /// The mode used to draw the sprite's image. public func setDrawMode(_ mode: Graphics.DrawMode) { - spriteAPI.setDrawMode.unsafelyUnwrapped(pointer, mode.cValue) + spriteAPI.pointee.setDrawMode.unsafelyUnwrapped(pointer, mode.cValue) } /// How the sprite's image is mirrored when drawn. public var imageFlip: Graphics.BitmapFlip { - get { Graphics.BitmapFlip(spriteAPI.getImageFlip.unsafelyUnwrapped(pointer)) } - set { spriteAPI.setImageFlip.unsafelyUnwrapped(pointer, newValue.cValue) } + get { Graphics.BitmapFlip(spriteAPI.pointee.getImageFlip.unsafelyUnwrapped(pointer)) } + set { spriteAPI.pointee.setImageFlip.unsafelyUnwrapped(pointer, newValue.cValue) } } /// Sets the stencil applied when drawing the sprite. If `tile` is /// `true` the image width must be a multiple of 32. public func setStencil(_ stencil: Graphics.Bitmap?, tile: Bool = false) { retainedStencil = stencil - spriteAPI.setStencilImage.unsafelyUnwrapped(pointer, stencil?.pointer, tile ? 1 : 0) + spriteAPI.pointee.setStencilImage.unsafelyUnwrapped(pointer, stencil?.pointer, tile ? 1 : 0) } /// Sets an 8×8 stencil pattern (8 rows of image data). public func setStencilPattern(_ rows: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) { var pattern: [UInt8] = [rows.0, rows.1, rows.2, rows.3, rows.4, rows.5, rows.6, rows.7] pattern.withUnsafeMutableBufferPointer { buffer in - spriteAPI.setStencilPattern.unsafelyUnwrapped(pointer, buffer.baseAddress) + spriteAPI.pointee.setStencilPattern.unsafelyUnwrapped(pointer, buffer.baseAddress) } } public func clearStencil() { retainedStencil = nil - spriteAPI.clearStencil.unsafelyUnwrapped(pointer) + spriteAPI.pointee.clearStencil.unsafelyUnwrapped(pointer) } /// Clips the sprite's drawing to `rect` (screen coordinates). public func setClipRect(_ rect: Graphics.Rect) { - spriteAPI.setClipRect.unsafelyUnwrapped(pointer, rect.cValue) + spriteAPI.pointee.setClipRect.unsafelyUnwrapped(pointer, rect.cValue) } public func clearClipRect() { - spriteAPI.clearClipRect.unsafelyUnwrapped(pointer) + spriteAPI.pointee.clearClipRect.unsafelyUnwrapped(pointer) } /// Clips all sprites with z-index in `startZ...endZ` to `rect`. public static func setClipRectsInRange(_ rect: Graphics.Rect, startZ: Int, endZ: Int) { - spriteAPI.setClipRectsInRange.unsafelyUnwrapped(rect.cValue, Int32(startZ), Int32(endZ)) + spriteAPI.pointee.setClipRectsInRange.unsafelyUnwrapped(rect.cValue, Int32(startZ), Int32(endZ)) } public static func clearClipRectsInRange(startZ: Int, endZ: Int) { - spriteAPI.clearClipRectsInRange.unsafelyUnwrapped(Int32(startZ), Int32(endZ)) + spriteAPI.pointee.clearClipRectsInRange.unsafelyUnwrapped(Int32(startZ), Int32(endZ)) } // MARK: - Behavior flags /// Whether the sprite's update function is called by `updateAndDrawAll()`. public var updatesEnabled: Bool { - get { spriteAPI.updatesEnabled.unsafelyUnwrapped(pointer) != 0 } - set { spriteAPI.setUpdatesEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) } + get { spriteAPI.pointee.updatesEnabled.unsafelyUnwrapped(pointer) != 0 } + set { spriteAPI.pointee.setUpdatesEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) } } public var collisionsEnabled: Bool { - get { spriteAPI.collisionsEnabled.unsafelyUnwrapped(pointer) != 0 } - set { spriteAPI.setCollisionsEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) } + get { spriteAPI.pointee.collisionsEnabled.unsafelyUnwrapped(pointer) != 0 } + set { spriteAPI.pointee.setCollisionsEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) } } public var isVisible: Bool { - get { spriteAPI.isVisible.unsafelyUnwrapped(pointer) != 0 } - set { spriteAPI.setVisible.unsafelyUnwrapped(pointer, newValue ? 1 : 0) } + get { spriteAPI.pointee.isVisible.unsafelyUnwrapped(pointer) != 0 } + set { spriteAPI.pointee.setVisible.unsafelyUnwrapped(pointer, newValue ? 1 : 0) } } /// Marking a sprite opaque tells the system it does not need to redraw /// anything behind it. public func setOpaque(_ flag: Bool) { - spriteAPI.setOpaque.unsafelyUnwrapped(pointer, flag ? 1 : 0) + spriteAPI.pointee.setOpaque.unsafelyUnwrapped(pointer, flag ? 1 : 0) } /// Forces the sprite to redraw this frame. public func markDirty() { - spriteAPI.markDirty.unsafelyUnwrapped(pointer) + spriteAPI.pointee.markDirty.unsafelyUnwrapped(pointer) } /// Marks part of the sprite (in sprite-local coordinates) as needing /// a redraw. public func markDirty(rect: Rect) { - spriteAPI.markDirtyRect.unsafelyUnwrapped(pointer, rect.cValue) + spriteAPI.pointee.markDirtyRect.unsafelyUnwrapped(pointer, rect.cValue) } /// An integer tag for identifying sprites (e.g. in collisions). public var tag: UInt8 { - get { spriteAPI.getTag.unsafelyUnwrapped(pointer) } - set { spriteAPI.setTag.unsafelyUnwrapped(pointer, newValue) } + get { spriteAPI.pointee.getTag.unsafelyUnwrapped(pointer) } + set { spriteAPI.pointee.setTag.unsafelyUnwrapped(pointer, newValue) } } /// When `true`, the sprite draws in screen coordinates, ignoring the /// global draw offset. public func setIgnoresDrawOffset(_ flag: Bool) { - spriteAPI.setIgnoresDrawOffset.unsafelyUnwrapped(pointer, flag ? 1 : 0) + spriteAPI.pointee.setIgnoresDrawOffset.unsafelyUnwrapped(pointer, flag ? 1 : 0) } // MARK: - Callbacks @@ -395,13 +395,13 @@ public final class Sprite { public func setUpdateFunction(_ update: ((Sprite) -> Void)?) { updateFunction = update if update != nil { - spriteAPI.setUpdateFunction.unsafelyUnwrapped(pointer, { spritePointer in + spriteAPI.pointee.setUpdateFunction.unsafelyUnwrapped(pointer, { spritePointer in guard let spritePointer else { return } let sprite = Sprite.wrapper(for: spritePointer) sprite.updateFunction?(sprite) }) } else { - spriteAPI.setUpdateFunction.unsafelyUnwrapped(pointer, nil) + spriteAPI.pointee.setUpdateFunction.unsafelyUnwrapped(pointer, nil) } } @@ -411,13 +411,13 @@ public final class Sprite { public func setDrawFunction(_ draw: ((Sprite, _ bounds: Rect, _ drawRect: Rect) -> Void)?) { drawFunction = draw if draw != nil { - spriteAPI.setDrawFunction.unsafelyUnwrapped(pointer, { spritePointer, bounds, drawRect in + spriteAPI.pointee.setDrawFunction.unsafelyUnwrapped(pointer, { spritePointer, bounds, drawRect in guard let spritePointer else { return } let sprite = Sprite.wrapper(for: spritePointer) sprite.drawFunction?(sprite, Rect(bounds), Rect(drawRect)) }) } else { - spriteAPI.setDrawFunction.unsafelyUnwrapped(pointer, nil) + spriteAPI.pointee.setDrawFunction.unsafelyUnwrapped(pointer, nil) } } @@ -425,17 +425,17 @@ public final class Sprite { /// Clears the collision world. Call when changing scenes. public static func resetCollisionWorld() { - spriteAPI.resetCollisionWorld.unsafelyUnwrapped() + spriteAPI.pointee.resetCollisionWorld.unsafelyUnwrapped() } /// The rect (in sprite-local coordinates) used for collisions. public var collideRect: Rect { - get { Rect(spriteAPI.getCollideRect.unsafelyUnwrapped(pointer)) } - set { spriteAPI.setCollideRect.unsafelyUnwrapped(pointer, newValue.cValue) } + get { Rect(spriteAPI.pointee.getCollideRect.unsafelyUnwrapped(pointer)) } + set { spriteAPI.pointee.setCollideRect.unsafelyUnwrapped(pointer, newValue.cValue) } } public func clearCollideRect() { - spriteAPI.clearCollideRect.unsafelyUnwrapped(pointer) + spriteAPI.pointee.clearCollideRect.unsafelyUnwrapped(pointer) } /// Sets the function deciding how this sprite responds when it @@ -443,14 +443,14 @@ public final class Sprite { public func setCollisionResponseFunction(_ filter: ((Sprite, _ other: Sprite) -> CollisionResponse)?) { collisionResponseFunction = filter if filter != nil { - spriteAPI.setCollisionResponseFunction.unsafelyUnwrapped(pointer, { spritePointer, otherPointer in + spriteAPI.pointee.setCollisionResponseFunction.unsafelyUnwrapped(pointer, { spritePointer, otherPointer in guard let spritePointer, let otherPointer else { return kCollisionTypeFreeze } let sprite = Sprite.wrapper(for: spritePointer) let other = Sprite.wrapper(for: otherPointer) return sprite.collisionResponseFunction?(sprite, other).cValue ?? kCollisionTypeFreeze }) } else { - spriteAPI.setCollisionResponseFunction.unsafelyUnwrapped(pointer, nil) + spriteAPI.pointee.setCollisionResponseFunction.unsafelyUnwrapped(pointer, nil) } } @@ -472,7 +472,7 @@ public final class Sprite { public func checkCollisions(goalX: Float, goalY: Float) -> (actual: (x: Float, y: Float), collisions: [CollisionInfo]) { var actualX: Float = 0, actualY: Float = 0, count: Int32 = 0 - let result = spriteAPI.checkCollisions.unsafelyUnwrapped( + let result = spriteAPI.pointee.checkCollisions.unsafelyUnwrapped( pointer, goalX, goalY, &actualX, &actualY, &count) return ((actualX, actualY), Sprite.collisionInfos(result, count: count)) } @@ -483,7 +483,7 @@ public final class Sprite { public func moveWithCollisions(goalX: Float, goalY: Float) -> (actual: (x: Float, y: Float), collisions: [CollisionInfo]) { var actualX: Float = 0, actualY: Float = 0, count: Int32 = 0 - let result = spriteAPI.moveWithCollisions.unsafelyUnwrapped( + let result = spriteAPI.pointee.moveWithCollisions.unsafelyUnwrapped( pointer, goalX, goalY, &actualX, &actualY, &count) return ((actualX, actualY), Sprite.collisionInfos(result, count: count)) } @@ -506,21 +506,21 @@ public final class Sprite { /// Sprites with collision rects containing the point. public static func query(atPoint x: Float, _ y: Float) -> [Sprite] { var count: Int32 = 0 - let result = spriteAPI.querySpritesAtPoint.unsafelyUnwrapped(x, y, &count) + let result = spriteAPI.pointee.querySpritesAtPoint.unsafelyUnwrapped(x, y, &count) return sprites(result, count: count) } /// Sprites with collision rects intersecting the rect. public static func query(inRect x: Float, _ y: Float, width: Float, height: Float) -> [Sprite] { var count: Int32 = 0 - let result = spriteAPI.querySpritesInRect.unsafelyUnwrapped(x, y, width, height, &count) + let result = spriteAPI.pointee.querySpritesInRect.unsafelyUnwrapped(x, y, width, height, &count) return sprites(result, count: count) } /// Sprites with collision rects intersecting the line segment. public static func query(alongLine x1: Float, _ y1: Float, _ x2: Float, _ y2: Float) -> [Sprite] { var count: Int32 = 0 - let result = spriteAPI.querySpritesAlongLine.unsafelyUnwrapped(x1, y1, x2, y2, &count) + let result = spriteAPI.pointee.querySpritesAlongLine.unsafelyUnwrapped(x1, y1, x2, y2, &count) return sprites(result, count: count) } @@ -528,7 +528,7 @@ public final class Sprite { public static func queryInfo(alongLine x1: Float, _ y1: Float, _ x2: Float, _ y2: Float) -> [QueryInfo] { var count: Int32 = 0 - guard let result = spriteAPI.querySpriteInfoAlongLine.unsafelyUnwrapped( + guard let result = spriteAPI.pointee.querySpriteInfoAlongLine.unsafelyUnwrapped( x1, y1, x2, y2, &count) else { return [] } var infos = [QueryInfo]() infos.reserveCapacity(Int(count)) @@ -542,14 +542,14 @@ public final class Sprite { /// Sprites whose collision rects overlap this sprite's. public var overlappingSprites: [Sprite] { var count: Int32 = 0 - let result = spriteAPI.overlappingSprites.unsafelyUnwrapped(pointer, &count) + let result = spriteAPI.pointee.overlappingSprites.unsafelyUnwrapped(pointer, &count) return Sprite.sprites(result, count: count) } /// All sprites in the display list that overlap another sprite. public static var allOverlappingSprites: [Sprite] { var count: Int32 = 0 - let result = spriteAPI.allOverlappingSprites.unsafelyUnwrapped(&count) + let result = spriteAPI.pointee.allOverlappingSprites.unsafelyUnwrapped(&count) return sprites(result, count: count) } } diff --git a/Sources/PlayDate/Support.swift b/Sources/PlayDate/Support.swift index 69cfac4..26f7dbf 100644 --- a/Sources/PlayDate/Support.swift +++ b/Sources/PlayDate/Support.swift @@ -24,14 +24,34 @@ extension String { self.init(playdateCString: pointer) } - /// Calls `body` with a temporary null-terminated UTF-8 copy of the string. + /// Calls `body` with a temporary null-terminated UTF-8 copy of the + /// string. The copy lives on the stack for short strings, so calling + /// this in the update loop does not churn the heap. func withPlaydateCString(_ body: (UnsafePointer) -> Result) -> Result { - var utf8 = ContiguousArray(self.utf8) - utf8.append(0) - return utf8.withUnsafeBufferPointer { buffer in - buffer.withMemoryRebound(to: CChar.self) { rebound in - body(rebound.baseAddress.unsafelyUnwrapped) + let count = utf8.count + return withUnsafeTemporaryAllocation(of: CChar.self, capacity: count + 1) { buffer in + var index = 0 + for byte in utf8 { + buffer[index] = CChar(bitPattern: byte) + index += 1 } + buffer[count] = 0 + return body(buffer.baseAddress.unsafelyUnwrapped) + } + } + + /// Calls `body` with a temporary buffer of the string's UTF-8 bytes (not + /// null-terminated) and its length, for the `(const void*, size_t)` text + /// APIs. Stack-allocated for short strings. + func withPlaydateUTF8(_ body: (UnsafeRawPointer, Int) -> Result) -> Result { + let count = utf8.count + return withUnsafeTemporaryAllocation(of: UInt8.self, capacity: count + 1) { buffer in + var index = 0 + for byte in utf8 { + buffer[index] = byte + index += 1 + } + return body(UnsafeRawPointer(buffer.baseAddress.unsafelyUnwrapped), count) } } diff --git a/Sources/PlayDate/System.swift b/Sources/PlayDate/System.swift index febebc3..11d378d 100644 --- a/Sources/PlayDate/System.swift +++ b/Sources/PlayDate/System.swift @@ -9,7 +9,7 @@ internal import CPlaydate public enum System {} extension System { - private static var api: playdate_sys { Playdate.api.system.pointee } + private static var api: UnsafePointer { Playdate.systemAPI } // MARK: - Types @@ -114,13 +114,13 @@ extension System { /// The system allocator. Pass `nil` to allocate, `size` 0 to free. @discardableResult public static func realloc(_ pointer: UnsafeMutableRawPointer?, size: Int) -> UnsafeMutableRawPointer? { - api.realloc.unsafelyUnwrapped(pointer, size) + api.pointee.realloc.unsafelyUnwrapped(pointer, size) } /// Frees memory that the Playdate OS handed to the caller (e.g. strings /// returned by `localizedText(forKey:)`). static func systemFree(_ pointer: UnsafeMutableRawPointer?) { - _ = api.realloc.unsafelyUnwrapped(pointer, 0) + _ = api.pointee.realloc.unsafelyUnwrapped(pointer, 0) } // MARK: - Logging @@ -137,48 +137,48 @@ extension System { // MARK: - Time - public static var language: Language { Language(api.getLanguage.unsafelyUnwrapped()) } + public static var language: Language { Language(api.pointee.getLanguage.unsafelyUnwrapped()) } /// Milliseconds since the game launched. Wraps around after about 49 days. public static var currentTimeMilliseconds: UInt32 { - UInt32(api.getCurrentTimeMilliseconds.unsafelyUnwrapped()) + UInt32(api.pointee.getCurrentTimeMilliseconds.unsafelyUnwrapped()) } /// Seconds (and sub-second milliseconds) since midnight 2000-01-01 UTC. public static var secondsSinceEpoch: (seconds: UInt32, milliseconds: UInt32) { var milliseconds: UInt32 = 0 let seconds = withUnsafeMutablePointer(to: &milliseconds) { - api.getSecondsSinceEpoch.unsafelyUnwrapped($0) + api.pointee.getSecondsSinceEpoch.unsafelyUnwrapped($0) } return (UInt32(seconds), milliseconds) } /// High-resolution timer value, in seconds. - public static var elapsedTime: Float { api.getElapsedTime.unsafelyUnwrapped() } + public static var elapsedTime: Float { api.pointee.getElapsedTime.unsafelyUnwrapped() } - public static func resetElapsedTime() { api.resetElapsedTime.unsafelyUnwrapped() } + public static func resetElapsedTime() { api.pointee.resetElapsedTime.unsafelyUnwrapped() } /// Offset from UTC of the user-set timezone, in seconds. - public static var timezoneOffset: Int32 { api.getTimezoneOffset.unsafelyUnwrapped() } + public static var timezoneOffset: Int32 { api.pointee.getTimezoneOffset.unsafelyUnwrapped() } public static var shouldDisplay24HourTime: Bool { - api.shouldDisplay24HourTime.unsafelyUnwrapped() != 0 + api.pointee.shouldDisplay24HourTime.unsafelyUnwrapped() != 0 } public static func convertEpochToDateTime(_ epoch: UInt32) -> DateTime { var dateTime = PDDateTime() - api.convertEpochToDateTime.unsafelyUnwrapped(epoch, &dateTime) + api.pointee.convertEpochToDateTime.unsafelyUnwrapped(epoch, &dateTime) return DateTime(dateTime) } public static func convertDateTimeToEpoch(_ dateTime: DateTime) -> UInt32 { var cValue = dateTime.cValue - return api.convertDateTimeToEpoch.unsafelyUnwrapped(&cValue) + return api.pointee.convertDateTimeToEpoch.unsafelyUnwrapped(&cValue) } /// Blocks execution for the given number of milliseconds. public static func delay(milliseconds: UInt32) { - api.delay.unsafelyUnwrapped(milliseconds) + api.pointee.delay.unsafelyUnwrapped(milliseconds) } /// Requests the server time. The completion receives the time string or @@ -186,7 +186,7 @@ extension System { /// before the first completes replaces the stored completion. public static func getServerTime(_ completion: @escaping (_ time: String?, _ error: String?) -> Void) { serverTimeCompletion = completion - api.getServerTime.unsafelyUnwrapped { time, error in + api.pointee.getServerTime.unsafelyUnwrapped { time, error in let completion = System.serverTimeCompletion System.serverTimeCompletion = nil completion?(String(playdateCString: time), String(playdateCString: error)) @@ -200,7 +200,7 @@ extension System { /// Sets the per-frame update callback. Return `true` to redraw the display. public static func setUpdateCallback(_ callback: @escaping () -> Bool) { updateCallback = callback - api.setUpdateCallback.unsafelyUnwrapped({ _ in + api.pointee.setUpdateCallback.unsafelyUnwrapped({ _ in System.updateCallback?() == true ? 1 : 0 }, nil) } @@ -209,7 +209,7 @@ extension System { /// Draws the current frames-per-second value at the given point. public static func drawFPS(x: Int = 0, y: Int = 0) { - api.drawFPS.unsafelyUnwrapped(Int32(x), Int32(y)) + api.pointee.drawFPS.unsafelyUnwrapped(Int32(x), Int32(y)) } // MARK: - Input @@ -217,7 +217,7 @@ extension System { /// The current button state: held, pressed this frame, released this frame. public static var buttonState: (current: Buttons, pushed: Buttons, released: Buttons) { var current = PDButtons(0), pushed = PDButtons(0), released = PDButtons(0) - api.getButtonState.unsafelyUnwrapped(¤t, &pushed, &released) + api.pointee.getButtonState.unsafelyUnwrapped(¤t, &pushed, &released) return (Buttons(current), Buttons(pushed), Buttons(released)) } @@ -228,47 +228,47 @@ extension System { _ callback: ((_ button: Buttons, _ isDown: Bool, _ when: UInt32) -> Int32)?) { buttonCallback = callback if callback != nil { - api.setButtonCallback.unsafelyUnwrapped({ button, down, when, _ in + api.pointee.setButtonCallback.unsafelyUnwrapped({ button, down, when, _ in System.buttonCallback?(Buttons(button), down != 0, when) ?? 0 }, nil, Int32(queueSize)) } else { - api.setButtonCallback.unsafelyUnwrapped(nil, nil, Int32(queueSize)) + api.pointee.setButtonCallback.unsafelyUnwrapped(nil, nil, Int32(queueSize)) } } nonisolated(unsafe) private static var buttonCallback: ((Buttons, Bool, UInt32) -> Int32)? public static func setPeripheralsEnabled(_ peripherals: Peripherals) { - api.setPeripheralsEnabled.unsafelyUnwrapped(PDPeripherals(peripherals.rawValue)) + api.pointee.setPeripheralsEnabled.unsafelyUnwrapped(PDPeripherals(peripherals.rawValue)) } /// The most recent accelerometer reading, in g. Enable the accelerometer /// with `setPeripheralsEnabled(.accelerometer)` first. public static var accelerometer: (x: Float, y: Float, z: Float) { var x: Float = 0, y: Float = 0, z: Float = 0 - api.getAccelerometer.unsafelyUnwrapped(&x, &y, &z) + api.pointee.getAccelerometer.unsafelyUnwrapped(&x, &y, &z) return (x, y, z) } /// Degrees the crank moved since the last frame. - public static var crankChange: Float { api.getCrankChange.unsafelyUnwrapped() } + public static var crankChange: Float { api.pointee.getCrankChange.unsafelyUnwrapped() } /// The crank position in degrees; 0 points along the +Y axis. - public static var crankAngle: Float { api.getCrankAngle.unsafelyUnwrapped() } + public static var crankAngle: Float { api.pointee.getCrankAngle.unsafelyUnwrapped() } - public static var isCrankDocked: Bool { api.isCrankDocked.unsafelyUnwrapped() != 0 } + public static var isCrankDocked: Bool { api.pointee.isCrankDocked.unsafelyUnwrapped() != 0 } /// Disables or enables the crank dock/undock sounds. Returns the previous setting. @discardableResult public static func setCrankSoundsDisabled(_ disabled: Bool) -> Bool { - api.setCrankSoundsDisabled.unsafelyUnwrapped(disabled ? 1 : 0) != 0 + api.pointee.setCrankSoundsDisabled.unsafelyUnwrapped(disabled ? 1 : 0) != 0 } /// Whether the user has the "flipped" system setting enabled. - public static var isFlipped: Bool { api.getFlipped.unsafelyUnwrapped() != 0 } + public static var isFlipped: Bool { api.pointee.getFlipped.unsafelyUnwrapped() != 0 } public static func setAutoLockDisabled(_ disabled: Bool) { - api.setAutoLockDisabled.unsafelyUnwrapped(disabled ? 1 : 0) + api.pointee.setAutoLockDisabled.unsafelyUnwrapped(disabled ? 1 : 0) } /// Installs a callback invoked when a message is received on the serial port @@ -276,12 +276,12 @@ extension System { public static func setSerialMessageCallback(_ callback: ((String) -> Void)?) { serialMessageCallback = callback if callback != nil { - api.setSerialMessageCallback.unsafelyUnwrapped { data in + api.pointee.setSerialMessageCallback.unsafelyUnwrapped { data in guard let message = String(playdateCString: data) else { return } System.serialMessageCallback?(message) } } else { - api.setSerialMessageCallback.unsafelyUnwrapped(nil) + api.pointee.setSerialMessageCallback.unsafelyUnwrapped(nil) } } @@ -311,11 +311,11 @@ extension System { /// The menu item's title. public var title: String { get { - String(playdateCString: Playdate.api.system.pointee.getMenuItemTitle.unsafelyUnwrapped(pointer)) ?? "" + String(playdateCString: Playdate.systemAPI.pointee.getMenuItemTitle.unsafelyUnwrapped(pointer)) ?? "" } set { newValue.withPlaydateCString { - Playdate.api.system.pointee.setMenuItemTitle.unsafelyUnwrapped(pointer, $0) + Playdate.systemAPI.pointee.setMenuItemTitle.unsafelyUnwrapped(pointer, $0) } } } @@ -323,8 +323,8 @@ extension System { /// For checkmark items this is 0 or 1; for option items it is the /// index of the selected option. public var value: Int { - get { Int(Playdate.api.system.pointee.getMenuItemValue.unsafelyUnwrapped(pointer)) } - set { Playdate.api.system.pointee.setMenuItemValue.unsafelyUnwrapped(pointer, Int32(newValue)) } + get { Int(Playdate.systemAPI.pointee.getMenuItemValue.unsafelyUnwrapped(pointer)) } + set { Playdate.systemAPI.pointee.setMenuItemValue.unsafelyUnwrapped(pointer, Int32(newValue)) } } /// Convenience view of `value` for checkmark items. @@ -352,7 +352,7 @@ extension System { public static func addMenuItem(title: String, onSelect: @escaping (MenuItem) -> Void) -> MenuItem? { var item: MenuItem? title.withPlaydateCString { cTitle in - let pointer = api.addMenuItem.unsafelyUnwrapped(cTitle, menuItemTrampoline, nil) + let pointer = api.pointee.addMenuItem.unsafelyUnwrapped(cTitle, menuItemTrampoline, nil) item = MenuItem(pointer: pointer, onSelect: onSelect) } return registered(item) @@ -364,7 +364,7 @@ extension System { onSelect: @escaping (MenuItem) -> Void) -> MenuItem? { var item: MenuItem? title.withPlaydateCString { cTitle in - let pointer = api.addCheckmarkMenuItem.unsafelyUnwrapped( + let pointer = api.pointee.addCheckmarkMenuItem.unsafelyUnwrapped( cTitle, isChecked ? 1 : 0, menuItemTrampoline, nil) item = MenuItem(pointer: pointer, onSelect: onSelect) } @@ -382,7 +382,7 @@ extension System { var item: MenuItem? title.withPlaydateCString { cTitle in cOptions.withUnsafeMutableBufferPointer { buffer in - let pointer = api.addOptionsMenuItem.unsafelyUnwrapped( + let pointer = api.pointee.addOptionsMenuItem.unsafelyUnwrapped( cTitle, buffer.baseAddress, Int32(options.count), menuItemTrampoline, nil) item = MenuItem(pointer: pointer, retainedOptionTitles: copies, onSelect: onSelect) } @@ -393,20 +393,20 @@ extension System { /// Registers the wrapper as the item's userdata and keeps it alive. private static func registered(_ item: MenuItem?) -> MenuItem? { guard let item else { return nil } - api.setMenuItemUserdata.unsafelyUnwrapped( + api.pointee.setMenuItemUserdata.unsafelyUnwrapped( item.pointer, Unmanaged.passUnretained(item).toOpaque()) liveMenuItems.append(item) return item } public static func removeMenuItem(_ item: MenuItem) { - api.removeMenuItem.unsafelyUnwrapped(item.pointer) + api.pointee.removeMenuItem.unsafelyUnwrapped(item.pointer) item.deallocateRetainedTitles() liveMenuItems.removeAll { $0 === item } } public static func removeAllMenuItems() { - api.removeAllMenuItems.unsafelyUnwrapped() + api.pointee.removeAllMenuItems.unsafelyUnwrapped() for item in liveMenuItems { item.deallocateRetainedTitles() } liveMenuItems = [] } @@ -414,35 +414,35 @@ extension System { /// Sets a custom image for the pause menu, optionally shifted left by /// `xOffset` (0...200). public static func setMenuImage(_ bitmap: Graphics.Bitmap?, xOffset: Int = 0) { - api.setMenuImage.unsafelyUnwrapped(bitmap?.pointer, Int32(xOffset)) + api.pointee.setMenuImage.unsafelyUnwrapped(bitmap?.pointer, Int32(xOffset)) } // MARK: - Device state /// Whether the user has enabled the "reduce flashing" accessibility setting. - public static var reduceFlashing: Bool { api.getReduceFlashing.unsafelyUnwrapped() != 0 } + public static var reduceFlashing: Bool { api.pointee.getReduceFlashing.unsafelyUnwrapped() != 0 } /// Battery charge, 0...100. - public static var batteryPercentage: Float { api.getBatteryPercentage.unsafelyUnwrapped() } + public static var batteryPercentage: Float { api.pointee.getBatteryPercentage.unsafelyUnwrapped() } - public static var batteryVoltage: Float { api.getBatteryVoltage.unsafelyUnwrapped() } + public static var batteryVoltage: Float { api.pointee.getBatteryVoltage.unsafelyUnwrapped() } /// Flushes the CPU instruction cache after loading code at runtime. - public static func clearICache() { api.clearICache.unsafelyUnwrapped() } + public static func clearICache() { api.pointee.clearICache.unsafelyUnwrapped() } /// Quits the current game and restarts it with the given launch arguments. public static func restartGame(launchArguments: String? = nil) { if let launchArguments { - launchArguments.withPlaydateCString { api.restartGame.unsafelyUnwrapped($0) } + launchArguments.withPlaydateCString { api.pointee.restartGame.unsafelyUnwrapped($0) } } else { - api.restartGame.unsafelyUnwrapped(nil) + api.pointee.restartGame.unsafelyUnwrapped(nil) } } /// The arguments the game was launched with, and the path of the pdx. public static var launchArguments: (arguments: String?, path: String?) { var path: UnsafePointer? - let arguments = api.getLaunchArgs.unsafelyUnwrapped(&path) + let arguments = api.pointee.getLaunchArgs.unsafelyUnwrapped(&path) return (String(playdateCString: arguments), String(playdateCString: path)) } @@ -450,12 +450,12 @@ extension System { /// not active or the send fails. @discardableResult public static func sendMirrorData(command: UInt8, data: UnsafeMutableRawBufferPointer) -> Bool { - api.sendMirrorData.unsafelyUnwrapped(command, data.baseAddress, Int32(data.count)) + api.pointee.sendMirrorData.unsafelyUnwrapped(command, data.baseAddress, Int32(data.count)) } /// OS, language, and pdx version information. public static var info: Info { - let info = api.getSystemInfo.unsafelyUnwrapped().unsafelyUnwrapped.pointee + let info = api.pointee.getSystemInfo.unsafelyUnwrapped().unsafelyUnwrapped.pointee return Info(osVersion: info.osversion, language: Language(info.language), pdxVersion: info.pdxversion) @@ -464,7 +464,7 @@ extension System { /// Looks up a localized string by key from the game's strings files. public static func localizedText(forKey key: String, language: Language = .system) -> String? { key.withPlaydateCString { cKey in - guard let cString = api.getLocalizedText.unsafelyUnwrapped(cKey, language.cValue) else { + guard let cString = api.pointee.getLocalizedText.unsafelyUnwrapped(cKey, language.cValue) else { return nil } let text = String(playdateCString: cString) @@ -474,12 +474,12 @@ extension System { } /// The system volume, 0...1. - public static var volume: Float { api.getVolume.unsafelyUnwrapped() } + public static var volume: Float { api.pointee.getVolume.unsafelyUnwrapped() } public static var powerStatus: PowerStatus { - PowerStatus(rawValue: api.getPowerStatus.unsafelyUnwrapped().rawValue) + PowerStatus(rawValue: api.pointee.getPowerStatus.unsafelyUnwrapped().rawValue) } /// Quits the game and returns to the launcher. - public static func exitToLauncher() { api.exitToLauncher.unsafelyUnwrapped() } + public static func exitToLauncher() { api.pointee.exitToLauncher.unsafelyUnwrapped() } }