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 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 07:23:19 +02:00
co-authored by Claude Fable 5
parent e4e99cc894
commit d914e0f8fb
20 changed files with 682 additions and 643 deletions
+11 -11
View File
@@ -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_display> { 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))
}
}