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
@@ -5,7 +5,7 @@
internal import CPlaydate
private var tilemapAPI: playdate_tilemap { gfx.tilemap.pointee }
private var tilemapAPI: UnsafePointer<playdate_tilemap> { 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)
}
}
}