Promote subsystem namespaces to the top level of the module
System, Display, Graphics, Sprite, Sound, File, JSON, Lua, Scoreboards, Network, Rect, SystemEvent, and AccessReply now live at module scope instead of being nested in the Playdate enum; consumers write System.log(...) instead of Playdate.System.log(...), and can qualify with the module name (PlayDate.System) on collision. The Playdate enum remains only as the raw C API bootstrap (initialize(with:), api, apiPointer), and Playdate.Error is renamed PlaydateError so no top-level type shadows Swift.Error. Breaking change for any existing consumers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,16 +15,16 @@ All ten C subsystems are covered:
|
||||
|
||||
| Namespace | Wraps | Highlights |
|
||||
|---|---|---|
|
||||
| `Playdate.System` | `playdate->system` | input, time, menu items, logging |
|
||||
| `Playdate.Display` | `playdate->display` | refresh rate, scale, mosaic, flip |
|
||||
| `Playdate.Graphics` | `playdate->graphics` | drawing, `Bitmap`, `Font`, `TileMap`, video |
|
||||
| `Playdate.Sprite` | `playdate->sprite` | display list, collisions, custom draw |
|
||||
| `Playdate.Sound` | `playdate->sound` | players, synths, sequences, effects |
|
||||
| `Playdate.File` | `playdate->file` | `Handle`, directory operations |
|
||||
| `Playdate.JSON` | `playdate->json` | `Value` tree decode/encode |
|
||||
| `Playdate.Lua` | `playdate->lua` | C functions, classes, stack access |
|
||||
| `Playdate.Scoreboards` | `playdate->scoreboards` | online leaderboards |
|
||||
| `Playdate.Network` | `playdate->network` | wifi, `HTTPConnection`, `TCPConnection` |
|
||||
| `System` | `playdate->system` | input, time, menu items, logging |
|
||||
| `Display` | `playdate->display` | refresh rate, scale, mosaic, flip |
|
||||
| `Graphics` | `playdate->graphics` | drawing, `Bitmap`, `Font`, `TileMap`, video |
|
||||
| `Sprite` | `playdate->sprite` | display list, collisions, custom draw |
|
||||
| `Sound` | `playdate->sound` | players, synths, sequences, effects |
|
||||
| `File` | `playdate->file` | `Handle`, directory operations |
|
||||
| `JSON` | `playdate->json` | `Value` tree decode/encode |
|
||||
| `Lua` | `playdate->lua` | C functions, classes, stack access |
|
||||
| `Scoreboards` | `playdate->scoreboards` | online leaderboards |
|
||||
| `Network` | `playdate->network` | wifi, `HTTPConnection`, `TCPConnection` |
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -82,7 +82,7 @@ import PlayDate
|
||||
func eventHandler(pointer: UnsafeMutableRawPointer,
|
||||
event: PDSystemEvent,
|
||||
argument: UInt32) -> Int32 {
|
||||
switch Playdate.SystemEvent(event: event, argument: argument) {
|
||||
switch SystemEvent(event: event, argument: argument) {
|
||||
case .initialize:
|
||||
Playdate.initialize(with: pointer) // must happen before anything else
|
||||
Game.shared.start()
|
||||
@@ -97,25 +97,25 @@ func eventHandler(pointer: UnsafeMutableRawPointer,
|
||||
final class Game {
|
||||
nonisolated(unsafe) static let shared = Game()
|
||||
|
||||
var player = Playdate.Sprite()
|
||||
var player = Sprite()
|
||||
|
||||
func start() {
|
||||
Playdate.Display.setRefreshRate(50)
|
||||
Display.setRefreshRate(50)
|
||||
|
||||
Playdate.System.setUpdateCallback {
|
||||
System.setUpdateCallback {
|
||||
self.update()
|
||||
return true // true = redraw the display this frame
|
||||
}
|
||||
}
|
||||
|
||||
func update() {
|
||||
let (_, pushed, _) = Playdate.System.buttonState
|
||||
let (_, pushed, _) = System.buttonState
|
||||
if pushed.contains(.a) {
|
||||
Playdate.System.log("A pressed at \(Playdate.System.currentTimeMilliseconds)ms")
|
||||
System.log("A pressed at \(System.currentTimeMilliseconds)ms")
|
||||
}
|
||||
|
||||
Playdate.Sprite.updateAndDrawAll()
|
||||
Playdate.System.drawFPS()
|
||||
Sprite.updateAndDrawAll()
|
||||
System.drawFPS()
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -130,68 +130,68 @@ a programmer error and will crash.
|
||||
|
||||
```swift
|
||||
// Buttons are an OptionSet: current (held), pushed and released this frame.
|
||||
let (current, pushed, released) = Playdate.System.buttonState
|
||||
let (current, pushed, released) = System.buttonState
|
||||
if current.contains([.b, .down]) { /* charge shot */ }
|
||||
|
||||
// Crank.
|
||||
if !Playdate.System.isCrankDocked {
|
||||
aim(degrees: Playdate.System.crankAngle)
|
||||
spin(by: Playdate.System.crankChange)
|
||||
if !System.isCrankDocked {
|
||||
aim(degrees: System.crankAngle)
|
||||
spin(by: System.crankChange)
|
||||
}
|
||||
|
||||
// Accelerometer is a peripheral you enable first.
|
||||
Playdate.System.setPeripheralsEnabled(.accelerometer)
|
||||
let (x, y, z) = Playdate.System.accelerometer
|
||||
System.setPeripheralsEnabled(.accelerometer)
|
||||
let (x, y, z) = System.accelerometer
|
||||
|
||||
// System menu items take closures; the binding keeps them alive until removed.
|
||||
Playdate.System.addCheckmarkMenuItem(title: "music", isChecked: true) { item in
|
||||
System.addCheckmarkMenuItem(title: "music", isChecked: true) { item in
|
||||
Audio.musicEnabled = item.isChecked
|
||||
}
|
||||
Playdate.System.addOptionsMenuItem(title: "mode", options: ["easy", "hard"]) { item in
|
||||
System.addOptionsMenuItem(title: "mode", options: ["easy", "hard"]) { item in
|
||||
Game.shared.difficulty = item.value
|
||||
}
|
||||
|
||||
// Logging goes to the simulator console or device serial.
|
||||
Playdate.System.log("spawned \(count) enemies")
|
||||
Playdate.System.error("unrecoverable") // stops execution
|
||||
System.log("spawned \(count) enemies")
|
||||
System.error("unrecoverable") // stops execution
|
||||
```
|
||||
|
||||
### Graphics: drawing, bitmaps, fonts
|
||||
|
||||
Fallible loads (`Bitmap(path:)`, `Font(path:)`, …) throw `Playdate.Error`,
|
||||
Fallible loads (`Bitmap(path:)`, `Font(path:)`, …) throw `PlaydateError`,
|
||||
which carries the message produced by the OS:
|
||||
|
||||
```swift
|
||||
let font = try Playdate.Graphics.Font(path: "fonts/Asheville-Sans-14-Bold.pft")
|
||||
Playdate.Graphics.setFont(font)
|
||||
let font = try Graphics.Font(path: "fonts/Asheville-Sans-14-Bold.pft")
|
||||
Graphics.setFont(font)
|
||||
|
||||
Playdate.Graphics.clear(color: .white)
|
||||
Playdate.Graphics.fillRect(x: 0, y: 0, width: 400, height: 32, color: .black)
|
||||
Playdate.Graphics.drawText("Hëllo, Playdate", x: 8, y: 8)
|
||||
Graphics.clear(color: .white)
|
||||
Graphics.fillRect(x: 0, y: 0, width: 400, height: 32, color: .black)
|
||||
Graphics.drawText("Hëllo, Playdate", x: 8, y: 8)
|
||||
|
||||
// Colors are solid or 8×8 patterns.
|
||||
let checker = Playdate.Graphics.Pattern(rows: (0xAA, 0x55, 0xAA, 0x55,
|
||||
0xAA, 0x55, 0xAA, 0x55))
|
||||
Playdate.Graphics.fillEllipse(x: 100, y: 100, width: 64, height: 64,
|
||||
color: .pattern(checker))
|
||||
let checker = Graphics.Pattern(rows: (0xAA, 0x55, 0xAA, 0x55,
|
||||
0xAA, 0x55, 0xAA, 0x55))
|
||||
Graphics.fillEllipse(x: 100, y: 100, width: 64, height: 64,
|
||||
color: .pattern(checker))
|
||||
|
||||
// Bitmaps draw themselves; draw into one by pushing it as the context.
|
||||
let logo = try Playdate.Graphics.Bitmap(path: "images/logo")
|
||||
let logo = try Graphics.Bitmap(path: "images/logo")
|
||||
logo.draw(x: 168, y: 88)
|
||||
|
||||
let canvas = Playdate.Graphics.Bitmap(width: 64, height: 64)
|
||||
Playdate.Graphics.pushContext(canvas)
|
||||
Playdate.Graphics.drawLine(x1: 0, y1: 0, x2: 63, y2: 63, width: 2, color: .black)
|
||||
Playdate.Graphics.popContext()
|
||||
let canvas = Graphics.Bitmap(width: 64, height: 64)
|
||||
Graphics.pushContext(canvas)
|
||||
Graphics.drawLine(x1: 0, y1: 0, x2: 63, y2: 63, width: 2, color: .black)
|
||||
Graphics.popContext()
|
||||
```
|
||||
|
||||
### Sprites and collisions
|
||||
|
||||
```swift
|
||||
let ball = Playdate.Sprite()
|
||||
ball.setImage(try Playdate.Graphics.Bitmap(path: "images/ball"))
|
||||
let ball = Sprite()
|
||||
ball.setImage(try Graphics.Bitmap(path: "images/ball"))
|
||||
ball.moveTo(x: 200, y: 120)
|
||||
ball.collideRect = Playdate.Rect(x: 0, y: 0, width: 16, height: 16)
|
||||
ball.collideRect = Rect(x: 0, y: 0, width: 16, height: 16)
|
||||
ball.setCollisionResponseFunction { _, _ in .bounce }
|
||||
ball.add() // adds to the display list; the binding keeps it alive while added
|
||||
|
||||
@@ -211,29 +211,29 @@ sprite userdata slot is reserved by the binding for that recovery — use the
|
||||
|
||||
```swift
|
||||
// Stream music from disk.
|
||||
let music = try Playdate.Sound.FilePlayer(path: "audio/theme")
|
||||
let music = try Sound.FilePlayer(path: "audio/theme")
|
||||
music.play(repeat: 0) // 0 = loop forever
|
||||
|
||||
// Play short effects from memory.
|
||||
let blip = try Playdate.Sound.SamplePlayer(path: "audio/blip")
|
||||
let blip = try Sound.SamplePlayer(path: "audio/blip")
|
||||
blip.play()
|
||||
|
||||
// Synthesis.
|
||||
let synth = Playdate.Sound.Synth(waveform: .square)
|
||||
let synth = Sound.Synth(waveform: .square)
|
||||
synth.setAttackTime(0.01)
|
||||
synth.setReleaseTime(0.2)
|
||||
synth.playMIDINote(Playdate.Sound.noteC4, velocity: 0.8, length: 0.5)
|
||||
synth.playMIDINote(Sound.noteC4, velocity: 0.8, length: 0.5)
|
||||
|
||||
// Channels mix sources and effects.
|
||||
let channel = Playdate.Sound.Channel()
|
||||
let channel = Sound.Channel()
|
||||
channel.add()
|
||||
channel.addSource(synth)
|
||||
let filter = Playdate.Sound.TwoPoleFilter(kind: .lowPass)
|
||||
let filter = Sound.TwoPoleFilter(kind: .lowPass)
|
||||
filter.setFrequency(800)
|
||||
channel.addEffect(filter)
|
||||
|
||||
// Anything that takes a modulator accepts any SignalValue (LFO, Envelope, …).
|
||||
let wobble = Playdate.Sound.LFO(shape: .sine)
|
||||
let wobble = Sound.LFO(shape: .sine)
|
||||
wobble.setRate(2)
|
||||
synth.frequencyModulator = wobble
|
||||
```
|
||||
@@ -242,20 +242,20 @@ synth.frequencyModulator = wobble
|
||||
|
||||
```swift
|
||||
// Paths resolve against the game's Data directory and pdx per the open mode.
|
||||
let save = try Playdate.File.Handle(path: "save.json", mode: .write)
|
||||
try save.write(Playdate.JSON.encode(.table([
|
||||
let save = try File.Handle(path: "save.json", mode: .write)
|
||||
try save.write(JSON.encode(.table([
|
||||
"level": .int(3),
|
||||
"name": .string("Röck"),
|
||||
])))
|
||||
try save.close()
|
||||
|
||||
let loaded = try Playdate.JSON.decodeFile(at: "save.json")
|
||||
let loaded = try JSON.decodeFile(at: "save.json")
|
||||
if case .table(let entries) = loaded, case .int(let level)? = entries["level"] {
|
||||
Game.shared.level = level
|
||||
}
|
||||
|
||||
try Playdate.File.listFiles(at: "replays") { name in
|
||||
Playdate.System.log("found \(name)")
|
||||
try File.listFiles(at: "replays") { name in
|
||||
System.log("found \(name)")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -264,14 +264,14 @@ try Playdate.File.listFiles(at: "replays") { name in
|
||||
Network access requires user permission per server:
|
||||
|
||||
```swift
|
||||
let reply = Playdate.Network.HTTPConnection.requestAccess(
|
||||
let reply = Network.HTTPConnection.requestAccess(
|
||||
server: "example.com", purpose: "Fetching daily puzzles") { allowed in
|
||||
guard allowed else { return }
|
||||
Puzzles.fetch()
|
||||
}
|
||||
|
||||
func fetch() {
|
||||
guard let connection = Playdate.Network.HTTPConnection(server: "example.com") else { return }
|
||||
guard let connection = Network.HTTPConnection(server: "example.com") else { return }
|
||||
connection.setRequestCompleteCallback { connection in
|
||||
let body = try? connection.read(length: connection.bytesAvailable)
|
||||
// … keep `connection` referenced somewhere until this fires …
|
||||
@@ -286,19 +286,22 @@ Lua callbacks are C function pointers with no context, so they must be
|
||||
`@convention(c)` functions rather than capturing closures:
|
||||
|
||||
```swift
|
||||
let double: Playdate.Lua.CFunction = { _ in
|
||||
Playdate.Lua.push(Playdate.Lua.intArgument(at: 1) * 2)
|
||||
let double: Lua.CFunction = { _ in
|
||||
Lua.push(Lua.intArgument(at: 1) * 2)
|
||||
return 1 // number of return values pushed
|
||||
}
|
||||
try Playdate.Lua.addFunction(double, name: "mylib.double")
|
||||
try Lua.addFunction(double, name: "mylib.double")
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Namespaces.** Everything lives under `Playdate`. Games that find that
|
||||
verbose can alias: `typealias Graphics = Playdate.Graphics`.
|
||||
- **Errors.** Fallible operations use typed throws — `throws(Playdate.Error)`
|
||||
generally, `throws(Playdate.Network.NetError)` for network I/O — so `catch`
|
||||
- **Namespaces.** The subsystem namespaces (`System`, `Graphics`, `Sound`,
|
||||
…) live at the top level of the module; only the raw C API bootstrap
|
||||
stays under `Playdate` (`Playdate.initialize(with:)`, `Playdate.api`).
|
||||
On a name collision with another module, qualify with the module name:
|
||||
`PlayDate.System`.
|
||||
- **Errors.** Fallible operations use typed throws — `throws(PlaydateError)`
|
||||
generally, `throws(Network.NetError)` for network I/O — so `catch`
|
||||
gives you a concrete type, and no `any Error` existentials are needed.
|
||||
- **Ownership.** A wrapper that *creates* a C object frees it on `deinit`;
|
||||
keep the wrapper referenced for as long as you use it. Wrappers vending
|
||||
|
||||
@@ -37,8 +37,8 @@ import CPlaydate
|
||||
import PlayDate
|
||||
|
||||
// Touch a type from each module to prove both import and link.
|
||||
let event = Playdate.SystemEvent(event: kEventInit, argument: 0)
|
||||
let buttons: Playdate.System.Buttons = [.a, .up]
|
||||
let event = SystemEvent(event: kEventInit, argument: 0)
|
||||
let buttons: System.Buttons = [.a, .up]
|
||||
print(event != nil && buttons.contains(.a) ? "ok" : "broken")
|
||||
EOF
|
||||
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate {
|
||||
/// The display API: resolution, refresh rate, scaling, and effects.
|
||||
public enum Display {}
|
||||
}
|
||||
/// The display API: resolution, refresh rate, scaling, and effects.
|
||||
public enum Display {}
|
||||
|
||||
extension Playdate.Display {
|
||||
extension Display {
|
||||
private static var api: playdate_display { Playdate.api.display.pointee }
|
||||
|
||||
/// The display width in pixels, taking the current scale into account.
|
||||
|
||||
+22
-24
@@ -11,16 +11,14 @@ internal import CPlaydate
|
||||
private var fileAPI: playdate_file { Playdate.api.file.pointee }
|
||||
|
||||
/// The most recent file system error as a thrown error.
|
||||
private func lastFileError() -> Playdate.Error {
|
||||
Playdate.Error(cString: fileAPI.geterr.unsafelyUnwrapped())
|
||||
private func lastFileError() -> PlaydateError {
|
||||
PlaydateError(cString: fileAPI.geterr.unsafelyUnwrapped())
|
||||
}
|
||||
|
||||
extension Playdate {
|
||||
/// The file API: access to the game's Data directory and pdx contents.
|
||||
public enum File {}
|
||||
}
|
||||
/// The file API: access to the game's Data directory and pdx contents.
|
||||
public enum File {}
|
||||
|
||||
extension Playdate.File {
|
||||
extension File {
|
||||
// MARK: - Types
|
||||
|
||||
/// How to open a file.
|
||||
@@ -44,7 +42,7 @@ extension Playdate.File {
|
||||
public struct Stat: Sendable {
|
||||
public let isDirectory: Bool
|
||||
public let size: UInt32
|
||||
public let modified: Playdate.System.DateTime
|
||||
public let modified: System.DateTime
|
||||
}
|
||||
|
||||
/// The origin used by `Handle.seek(to:from:)`.
|
||||
@@ -59,7 +57,7 @@ extension Playdate.File {
|
||||
/// Calls `each` with the name of every file in `path`. Subdirectory names
|
||||
/// end in a slash. Throws if the directory does not exist.
|
||||
public static func listFiles(at path: String, showHidden: Bool = false,
|
||||
_ each: (String) -> Void) throws(Playdate.Error) {
|
||||
_ each: (String) -> Void) throws(PlaydateError) {
|
||||
let result = withoutActuallyEscaping(each) { each in
|
||||
var callback = each
|
||||
return path.withPlaydateCString { cPath in
|
||||
@@ -76,27 +74,27 @@ extension Playdate.File {
|
||||
}
|
||||
|
||||
/// Information about the file or directory at `path`.
|
||||
public static func stat(_ path: String) throws(Playdate.Error) -> Stat {
|
||||
public static func stat(_ path: String) throws(PlaydateError) -> Stat {
|
||||
var stat = FileStat()
|
||||
let result = path.withPlaydateCString { fileAPI.stat.unsafelyUnwrapped($0, &stat) }
|
||||
if result != 0 { throw lastFileError() }
|
||||
return Stat(
|
||||
isDirectory: stat.isdir != 0,
|
||||
size: stat.size,
|
||||
modified: Playdate.System.DateTime(
|
||||
modified: System.DateTime(
|
||||
year: UInt16(stat.m_year), month: UInt8(stat.m_month), day: UInt8(stat.m_day),
|
||||
hour: UInt8(stat.m_hour), minute: UInt8(stat.m_minute), second: UInt8(stat.m_second)))
|
||||
}
|
||||
|
||||
/// Creates a directory (and intermediate directories) in the Data directory.
|
||||
public static func mkdir(_ path: String) throws(Playdate.Error) {
|
||||
public static func mkdir(_ path: String) throws(PlaydateError) {
|
||||
let result = path.withPlaydateCString { fileAPI.mkdir.unsafelyUnwrapped($0) }
|
||||
if result != 0 { throw lastFileError() }
|
||||
}
|
||||
|
||||
/// Deletes the file or directory at `path`. Directories require
|
||||
/// `recursive` to be deleted with their contents.
|
||||
public static func unlink(_ path: String, recursive: Bool = false) throws(Playdate.Error) {
|
||||
public static func unlink(_ path: String, recursive: Bool = false) throws(PlaydateError) {
|
||||
let result = path.withPlaydateCString {
|
||||
fileAPI.unlink.unsafelyUnwrapped($0, recursive ? 1 : 0)
|
||||
}
|
||||
@@ -105,7 +103,7 @@ extension Playdate.File {
|
||||
|
||||
/// Renames (moves) a file in the Data directory, overwriting any existing
|
||||
/// file at the destination.
|
||||
public static func rename(from: String, to: String) throws(Playdate.Error) {
|
||||
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)
|
||||
@@ -123,7 +121,7 @@ extension Playdate.File {
|
||||
private var isClosed = false
|
||||
|
||||
/// Opens the file at `path`.
|
||||
public init(path: String, mode: Options) throws(Playdate.Error) {
|
||||
public init(path: String, mode: Options) throws(PlaydateError) {
|
||||
let pointer = path.withPlaydateCString {
|
||||
fileAPI.open.unsafelyUnwrapped($0, mode.cValue)
|
||||
}
|
||||
@@ -138,7 +136,7 @@ extension Playdate.File {
|
||||
}
|
||||
|
||||
/// Closes the file. Further operations are invalid.
|
||||
public func close() throws(Playdate.Error) {
|
||||
public func close() throws(PlaydateError) {
|
||||
guard !isClosed else { return }
|
||||
isClosed = true
|
||||
if fileAPI.close.unsafelyUnwrapped(pointer) != 0 { throw lastFileError() }
|
||||
@@ -146,7 +144,7 @@ extension Playdate.File {
|
||||
|
||||
/// 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(Playdate.Error) -> Int {
|
||||
public func read(into buffer: UnsafeMutableRawBufferPointer) throws(PlaydateError) -> Int {
|
||||
let result = fileAPI.read.unsafelyUnwrapped(
|
||||
pointer, buffer.baseAddress, UInt32(buffer.count))
|
||||
if result < 0 { throw lastFileError() }
|
||||
@@ -154,7 +152,7 @@ extension Playdate.File {
|
||||
}
|
||||
|
||||
/// Reads up to `length` bytes and returns them.
|
||||
public func read(length: Int) throws(Playdate.Error) -> [UInt8] {
|
||||
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))
|
||||
@@ -166,7 +164,7 @@ extension Playdate.File {
|
||||
|
||||
/// Writes the buffer to the file. Returns the number of bytes written.
|
||||
@discardableResult
|
||||
public func write(_ buffer: UnsafeRawBufferPointer) throws(Playdate.Error) -> Int {
|
||||
public func write(_ buffer: UnsafeRawBufferPointer) throws(PlaydateError) -> Int {
|
||||
let result = fileAPI.write.unsafelyUnwrapped(
|
||||
pointer, buffer.baseAddress, UInt32(buffer.count))
|
||||
if result < 0 { throw lastFileError() }
|
||||
@@ -175,7 +173,7 @@ extension Playdate.File {
|
||||
|
||||
/// Writes the bytes to the file. Returns the number of bytes written.
|
||||
@discardableResult
|
||||
public func write(_ bytes: [UInt8]) throws(Playdate.Error) -> Int {
|
||||
public func write(_ bytes: [UInt8]) throws(PlaydateError) -> Int {
|
||||
let result = bytes.withUnsafeBytes { buffer in
|
||||
fileAPI.write.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count))
|
||||
}
|
||||
@@ -185,27 +183,27 @@ extension Playdate.File {
|
||||
|
||||
/// Writes the string's UTF-8 to the file. Returns the bytes written.
|
||||
@discardableResult
|
||||
public func write(_ string: String) throws(Playdate.Error) -> Int {
|
||||
public func write(_ string: String) throws(PlaydateError) -> Int {
|
||||
try write(Array(string.utf8))
|
||||
}
|
||||
|
||||
/// Flushes buffered writes to disk. Returns the bytes written.
|
||||
@discardableResult
|
||||
public func flush() throws(Playdate.Error) -> Int {
|
||||
public func flush() throws(PlaydateError) -> Int {
|
||||
let result = fileAPI.flush.unsafelyUnwrapped(pointer)
|
||||
if result < 0 { throw lastFileError() }
|
||||
return Int(result)
|
||||
}
|
||||
|
||||
/// The current read/write offset.
|
||||
public func tell() throws(Playdate.Error) -> Int {
|
||||
public func tell() throws(PlaydateError) -> Int {
|
||||
let result = fileAPI.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(Playdate.Error) {
|
||||
public func seek(to offset: Int, from origin: SeekOrigin = .start) throws(PlaydateError) {
|
||||
if fileAPI.seek.unsafelyUnwrapped(pointer, Int32(offset), origin.rawValue) != 0 {
|
||||
throw lastFileError()
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate {
|
||||
/// The graphics API: drawing, bitmaps, fonts, tilemaps, and video.
|
||||
public enum Graphics {}
|
||||
}
|
||||
/// The graphics API: drawing, bitmaps, fonts, tilemaps, and video.
|
||||
public enum Graphics {}
|
||||
|
||||
var gfx: playdate_graphics { Playdate.api.graphics.pointee }
|
||||
|
||||
extension Playdate.Graphics {
|
||||
extension Graphics {
|
||||
// MARK: - Screen constants
|
||||
|
||||
/// The width of the screen in pixels (`LCD_COLUMNS`).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate.Graphics {
|
||||
extension Graphics {
|
||||
/// An image that can be drawn to the screen or used as a drawing target.
|
||||
/// Wraps `LCDBitmap`.
|
||||
public final class Bitmap {
|
||||
@@ -29,10 +29,10 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Loads a bitmap from a file in the game's pdx or Data directory.
|
||||
public convenience init(path: String) throws(Playdate.Error) {
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let pointer = path.withPlaydateCString { gfx.loadBitmap.unsafelyUnwrapped($0, &error) }
|
||||
guard let pointer else { throw Playdate.Error(cString: error) }
|
||||
guard let pointer else { throw PlaydateError(cString: error) }
|
||||
self.init(pointer: pointer, isOwned: true)
|
||||
}
|
||||
|
||||
@@ -74,10 +74,10 @@ extension Playdate.Graphics {
|
||||
// MARK: Operations
|
||||
|
||||
/// Replaces the bitmap's contents with the image at `path`.
|
||||
public func load(path: String) throws(Playdate.Error) {
|
||||
public func load(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
path.withPlaydateCString { gfx.loadIntoBitmap.unsafelyUnwrapped($0, pointer, &error) }
|
||||
if let error { throw Playdate.Error(cString: error) }
|
||||
if let error { throw PlaydateError(cString: error) }
|
||||
}
|
||||
|
||||
/// Fills the bitmap with `color`.
|
||||
@@ -166,10 +166,10 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Loads an image table from a file.
|
||||
public convenience init(path: String) throws(Playdate.Error) {
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let pointer = path.withPlaydateCString { gfx.loadBitmapTable.unsafelyUnwrapped($0, &error) }
|
||||
guard let pointer else { throw Playdate.Error(cString: error) }
|
||||
guard let pointer else { throw PlaydateError(cString: error) }
|
||||
self.init(pointer: pointer)
|
||||
}
|
||||
|
||||
@@ -178,10 +178,10 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Replaces the table's contents with the image table at `path`.
|
||||
public func load(path: String) throws(Playdate.Error) {
|
||||
public func load(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
path.withPlaydateCString { gfx.loadIntoBitmapTable.unsafelyUnwrapped($0, pointer, &error) }
|
||||
if let error { throw Playdate.Error(cString: error) }
|
||||
if let error { throw PlaydateError(cString: error) }
|
||||
}
|
||||
|
||||
/// The bitmap at `index`, or `nil` if out of range. The bitmap
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate.Graphics {
|
||||
extension Graphics {
|
||||
/// A font loaded from a .pft file. Wraps `LCDFont`.
|
||||
public final class Font {
|
||||
let pointer: OpaquePointer
|
||||
@@ -19,10 +19,10 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Loads a font from a file.
|
||||
public convenience init(path: String) throws(Playdate.Error) {
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let pointer = path.withPlaydateCString { gfx.loadFont.unsafelyUnwrapped($0, &error) }
|
||||
guard let pointer else { throw Playdate.Error(cString: error) }
|
||||
guard let pointer else { throw PlaydateError(cString: error) }
|
||||
self.init(pointer: pointer)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ extension Playdate.Graphics {
|
||||
|
||||
deinit {
|
||||
// Per the C API docs, fonts are freed with the system allocator.
|
||||
Playdate.System.systemFree(UnsafeMutableRawPointer(pointer))
|
||||
System.systemFree(UnsafeMutableRawPointer(pointer))
|
||||
retainedData?.deallocate()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ internal import CPlaydate
|
||||
|
||||
private var tilemapAPI: playdate_tilemap { gfx.tilemap.pointee }
|
||||
|
||||
extension Playdate.Graphics {
|
||||
extension Graphics {
|
||||
/// A grid of tiles drawn from a bitmap table. Wraps `LCDTileMap`.
|
||||
public final class TileMap {
|
||||
let pointer: OpaquePointer
|
||||
|
||||
@@ -9,7 +9,7 @@ internal import CPlaydate
|
||||
private var videoAPI: playdate_video { gfx.video.pointee }
|
||||
private var streamAPI: playdate_videostream { gfx.videostream.pointee }
|
||||
|
||||
extension Playdate.Graphics {
|
||||
extension Graphics {
|
||||
/// Plays .pdv video files. Wraps `LCDVideoPlayer`.
|
||||
public final class VideoPlayer {
|
||||
let pointer: OpaquePointer
|
||||
@@ -23,10 +23,10 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Opens the .pdv file at `path`.
|
||||
public convenience init(path: String) throws(Playdate.Error) {
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
let pointer = path.withPlaydateCString { videoAPI.loadVideo.unsafelyUnwrapped($0) }
|
||||
guard let pointer else {
|
||||
throw Playdate.Error(message: "unable to load video: \(path)")
|
||||
throw PlaydateError(message: "unable to load video: \(path)")
|
||||
}
|
||||
self.init(pointer: pointer, isOwned: true)
|
||||
}
|
||||
@@ -38,9 +38,9 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Sets the bitmap the video renders into.
|
||||
public func setContext(_ context: Bitmap) throws(Playdate.Error) {
|
||||
public func setContext(_ context: Bitmap) throws(PlaydateError) {
|
||||
guard videoAPI.setContext.unsafelyUnwrapped(pointer, context.pointer) != 0 else {
|
||||
throw Playdate.Error(message: error ?? "unable to set video context")
|
||||
throw PlaydateError(message: error ?? "unable to set video context")
|
||||
}
|
||||
retainedContext = context
|
||||
}
|
||||
@@ -58,9 +58,9 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Renders frame `frame` into the current context.
|
||||
public func renderFrame(_ frame: Int) throws(Playdate.Error) {
|
||||
public func renderFrame(_ frame: Int) throws(PlaydateError) {
|
||||
guard videoAPI.renderFrame.unsafelyUnwrapped(pointer, Int32(frame)) != 0 else {
|
||||
throw Playdate.Error(message: error ?? "unable to render frame \(frame)")
|
||||
throw PlaydateError(message: error ?? "unable to render frame \(frame)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,27 +100,27 @@ extension Playdate.Graphics {
|
||||
}
|
||||
|
||||
/// Streams from an open file.
|
||||
public func setFile(_ file: Playdate.File.Handle) {
|
||||
public func setFile(_ file: File.Handle) {
|
||||
retainedSource = file
|
||||
streamAPI.setFile.unsafelyUnwrapped(pointer, file.pointer)
|
||||
}
|
||||
|
||||
/// Streams from an HTTP connection.
|
||||
public func setHTTPConnection(_ connection: Playdate.Network.HTTPConnection) {
|
||||
public func setHTTPConnection(_ connection: Network.HTTPConnection) {
|
||||
retainedSource = connection
|
||||
streamAPI.setHTTPConnection.unsafelyUnwrapped(pointer, connection.pointer)
|
||||
}
|
||||
|
||||
/// Streams from a TCP connection.
|
||||
public func setTCPConnection(_ connection: Playdate.Network.TCPConnection) {
|
||||
public func setTCPConnection(_ connection: Network.TCPConnection) {
|
||||
retainedSource = connection
|
||||
streamAPI.setTCPConnection.unsafelyUnwrapped(pointer, connection.pointer)
|
||||
}
|
||||
|
||||
/// The player used for the stream's audio track. Owned by the stream.
|
||||
public var filePlayer: Playdate.Sound.FilePlayer? {
|
||||
public var filePlayer: Sound.FilePlayer? {
|
||||
guard let player = streamAPI.getFilePlayer.unsafelyUnwrapped(pointer) else { return nil }
|
||||
return Playdate.Sound.FilePlayer(pointer: player, isOwned: false)
|
||||
return Sound.FilePlayer(pointer: player, isOwned: false)
|
||||
}
|
||||
|
||||
/// The player used for the stream's video track. Owned by the stream.
|
||||
|
||||
+12
-14
@@ -11,12 +11,10 @@ internal import CPlaydate
|
||||
|
||||
private var jsonAPI: playdate_json { Playdate.api.json.pointee }
|
||||
|
||||
extension Playdate {
|
||||
/// The JSON API: decoding to and encoding from a `Value` tree.
|
||||
public enum JSON {}
|
||||
}
|
||||
/// The JSON API: decoding to and encoding from a `Value` tree.
|
||||
public enum JSON {}
|
||||
|
||||
extension Playdate.JSON {
|
||||
extension JSON {
|
||||
/// A decoded JSON value.
|
||||
public indirect enum Value {
|
||||
case null
|
||||
@@ -88,12 +86,12 @@ extension Playdate.JSON {
|
||||
decoder.didDecodeTableValue = { decoder, key, value in
|
||||
guard let userdata = decoder?.pointee.userdata else { return }
|
||||
let context = Unmanaged<DecodeContext>.fromOpaque(userdata).takeUnretainedValue()
|
||||
context.append(Playdate.JSON.convert(value), key: String(playdateCString: key))
|
||||
context.append(JSON.convert(value), key: String(playdateCString: key))
|
||||
}
|
||||
decoder.didDecodeArrayValue = { decoder, _, value in
|
||||
guard let userdata = decoder?.pointee.userdata else { return }
|
||||
let context = Unmanaged<DecodeContext>.fromOpaque(userdata).takeUnretainedValue()
|
||||
context.append(Playdate.JSON.convert(value), key: nil)
|
||||
context.append(JSON.convert(value), key: nil)
|
||||
}
|
||||
decoder.didDecodeSublist = { decoder, _, _ in
|
||||
guard let userdata = decoder?.pointee.userdata else { return nil }
|
||||
@@ -107,7 +105,7 @@ extension Playdate.JSON {
|
||||
}
|
||||
|
||||
/// Decodes a JSON string into a `Value` tree.
|
||||
public static func decode(_ jsonString: String) throws(Playdate.Error) -> Value {
|
||||
public static func decode(_ jsonString: String) throws(PlaydateError) -> Value {
|
||||
let context = DecodeContext()
|
||||
let unmanaged = Unmanaged.passUnretained(context)
|
||||
var decoder = makeDecoder(context: unmanaged)
|
||||
@@ -124,14 +122,14 @@ extension Playdate.JSON {
|
||||
}
|
||||
|
||||
/// Decodes JSON read from an open file into a `Value` tree.
|
||||
public static func decode(file: Playdate.File.Handle) throws(Playdate.Error) -> Value {
|
||||
public static func decode(file: File.Handle) throws(PlaydateError) -> Value {
|
||||
let context = DecodeContext()
|
||||
var decoder = makeDecoder(context: Unmanaged.passUnretained(context))
|
||||
var reader = json_reader()
|
||||
reader.userdata = Unmanaged.passUnretained(file).toOpaque()
|
||||
reader.read = { userdata, buffer, size in
|
||||
guard let userdata, let buffer else { return -1 }
|
||||
let file = Unmanaged<Playdate.File.Handle>.fromOpaque(userdata).takeUnretainedValue()
|
||||
let file = Unmanaged<File.Handle>.fromOpaque(userdata).takeUnretainedValue()
|
||||
let destination = UnsafeMutableRawBufferPointer(start: buffer, count: Int(size))
|
||||
do {
|
||||
let count = try file.read(into: destination)
|
||||
@@ -153,13 +151,13 @@ extension Playdate.JSON {
|
||||
}
|
||||
|
||||
/// Opens and decodes the JSON file at `path`.
|
||||
public static func decodeFile(at path: String) throws(Playdate.Error) -> Value {
|
||||
let file = try Playdate.File.Handle(path: path, mode: [.read, .readData])
|
||||
public static func decodeFile(at path: String) throws(PlaydateError) -> Value {
|
||||
let file = try File.Handle(path: path, mode: [.read, .readData])
|
||||
return try decode(file: file)
|
||||
}
|
||||
|
||||
private static func decodeError(_ context: DecodeContext) -> Playdate.Error {
|
||||
Playdate.Error(message: "\(context.errorMessage ?? "JSON decode failed") (line \(context.errorLine))")
|
||||
private static func decodeError(_ context: DecodeContext) -> PlaydateError {
|
||||
PlaydateError(message: "\(context.errorMessage ?? "JSON decode failed") (line \(context.errorLine))")
|
||||
}
|
||||
|
||||
// MARK: - Encoding
|
||||
|
||||
+16
-18
@@ -11,13 +11,11 @@ public import CPlaydate
|
||||
|
||||
private var luaAPI: playdate_lua { Playdate.api.lua.pointee }
|
||||
|
||||
extension Playdate {
|
||||
/// The Lua bridge: registering C functions and classes, and exchanging
|
||||
/// values with Lua code.
|
||||
public enum Lua {}
|
||||
}
|
||||
/// The Lua bridge: registering C functions and classes, and exchanging
|
||||
/// values with Lua code.
|
||||
public enum Lua {}
|
||||
|
||||
extension Playdate.Lua {
|
||||
extension Lua {
|
||||
/// A function callable from Lua. Returns the number of values it pushed
|
||||
/// onto the stack.
|
||||
public typealias CFunction = lua_CFunction
|
||||
@@ -58,12 +56,12 @@ extension Playdate.Lua {
|
||||
|
||||
/// Makes `function` callable from Lua as `name` (which may contain dots
|
||||
/// for namespacing, e.g. "mylib.myfunc").
|
||||
public static func addFunction(_ function: CFunction, name: String) throws(Playdate.Error) {
|
||||
public static func addFunction(_ function: CFunction, name: String) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let ok = name.withPlaydateCString {
|
||||
luaAPI.addFunction.unsafelyUnwrapped(function, $0, &error) != 0
|
||||
}
|
||||
if !ok { throw Playdate.Error(cString: error) }
|
||||
if !ok { throw PlaydateError(cString: error) }
|
||||
}
|
||||
|
||||
/// Registers a Lua class named `name` with the given methods and
|
||||
@@ -72,7 +70,7 @@ extension Playdate.Lua {
|
||||
public static func registerClass(name: String,
|
||||
functions: [(name: String, function: CFunction)],
|
||||
values: [ClassValue] = [],
|
||||
isStatic: Bool = false) throws(Playdate.Error) {
|
||||
isStatic: Bool = false) throws(PlaydateError) {
|
||||
// The registration tables are kept alive permanently: the OS
|
||||
// documents no copying guarantees for them.
|
||||
var registrations: [lua_reg] = functions.map { entry in
|
||||
@@ -107,7 +105,7 @@ extension Playdate.Lua {
|
||||
values.isEmpty ? nil : constantsBuffer,
|
||||
isStatic ? 1 : 0, &error) != 0
|
||||
}
|
||||
if !ok { throw Playdate.Error(cString: error) }
|
||||
if !ok { throw PlaydateError(cString: error) }
|
||||
}
|
||||
|
||||
/// Pushes a function onto the stack, e.g. for `setUserValue`.
|
||||
@@ -189,15 +187,15 @@ extension Playdate.Lua {
|
||||
|
||||
/// 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) -> Playdate.Graphics.Bitmap? {
|
||||
public static func bitmapArgument(at position: Int) -> Graphics.Bitmap? {
|
||||
guard let bitmap = luaAPI.getBitmap.unsafelyUnwrapped(Int32(position)) else { return nil }
|
||||
return Playdate.Graphics.Bitmap(pointer: bitmap, isOwned: false)
|
||||
return Graphics.Bitmap(pointer: bitmap, isOwned: false)
|
||||
}
|
||||
|
||||
/// The argument as a sprite.
|
||||
public static func spriteArgument(at position: Int) -> Playdate.Sprite? {
|
||||
public static func spriteArgument(at position: Int) -> Sprite? {
|
||||
guard let sprite = luaAPI.getSprite.unsafelyUnwrapped(Int32(position)) else { return nil }
|
||||
return Playdate.Sprite.wrapper(for: sprite)
|
||||
return Sprite.wrapper(for: sprite)
|
||||
}
|
||||
|
||||
// MARK: - Return values
|
||||
@@ -229,11 +227,11 @@ extension Playdate.Lua {
|
||||
}
|
||||
}
|
||||
|
||||
public static func push(_ bitmap: Playdate.Graphics.Bitmap) {
|
||||
public static func push(_ bitmap: Graphics.Bitmap) {
|
||||
luaAPI.pushBitmap.unsafelyUnwrapped(bitmap.pointer)
|
||||
}
|
||||
|
||||
public static func push(_ sprite: Playdate.Sprite) {
|
||||
public static func push(_ sprite: Sprite) {
|
||||
luaAPI.pushSprite.unsafelyUnwrapped(sprite.pointer)
|
||||
}
|
||||
|
||||
@@ -283,11 +281,11 @@ extension Playdate.Lua {
|
||||
|
||||
/// Calls the Lua function `name`. Push the arguments onto the stack
|
||||
/// first. Calling Lua from Swift has overhead; use sparingly.
|
||||
public static func callFunction(_ name: String, argumentCount: Int = 0) throws(Playdate.Error) {
|
||||
public static func callFunction(_ name: String, argumentCount: Int = 0) throws(PlaydateError) {
|
||||
var error: UnsafePointer<CChar>?
|
||||
let ok = name.withPlaydateCString {
|
||||
luaAPI.callFunction.unsafelyUnwrapped($0, Int32(argumentCount), &error) != 0
|
||||
}
|
||||
if !ok { throw Playdate.Error(cString: error) }
|
||||
if !ok { throw PlaydateError(cString: error) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,10 @@ 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 }
|
||||
|
||||
extension Playdate {
|
||||
/// The network API: wifi status, HTTP, and TCP.
|
||||
public enum Network {}
|
||||
}
|
||||
|
||||
extension Playdate.Network {
|
||||
public typealias AccessReply = Playdate.AccessReply
|
||||
/// The network API: wifi status, HTTP, and TCP.
|
||||
public enum Network {}
|
||||
|
||||
extension Network {
|
||||
/// A network error code (`PDNetErr`).
|
||||
public enum NetError: Int32, Swift.Error, Sendable {
|
||||
case noDevice = -1
|
||||
@@ -79,9 +75,9 @@ extension Playdate.Network {
|
||||
setEnabledCompletion = completion
|
||||
if completion != nil {
|
||||
networkAPI.setEnabled.unsafelyUnwrapped(enabled, { error in
|
||||
let completion = Playdate.Network.setEnabledCompletion
|
||||
Playdate.Network.setEnabledCompletion = nil
|
||||
completion?(Playdate.Network.optionalError(error))
|
||||
let completion = Network.setEnabledCompletion
|
||||
Network.setEnabledCompletion = nil
|
||||
completion?(Network.optionalError(error))
|
||||
})
|
||||
} else {
|
||||
networkAPI.setEnabled.unsafelyUnwrapped(enabled, nil)
|
||||
@@ -140,7 +136,7 @@ extension Playdate.Network {
|
||||
public static func requestAccess(server: String, port: Int = 443, useSSL: Bool = true,
|
||||
purpose: String? = nil,
|
||||
completion: @escaping (Bool) -> Void) -> AccessReply {
|
||||
Playdate.Network.requestAccess(
|
||||
Network.requestAccess(
|
||||
rawRequest: { httpAPI.requestAccess.unsafelyUnwrapped($0, $1, $2, $3, $4, $5) },
|
||||
server: server, port: port, useSSL: useSSL, purpose: purpose,
|
||||
completion: completion)
|
||||
@@ -205,7 +201,7 @@ extension Playdate.Network {
|
||||
httpAPI.get.unsafelyUnwrapped(pointer, cPath, cHeaders, headers.utf8.count)
|
||||
}
|
||||
}
|
||||
try Playdate.Network.check(error)
|
||||
try Network.check(error)
|
||||
}
|
||||
|
||||
/// Sends a POST request for `path` with the given body.
|
||||
@@ -220,7 +216,7 @@ extension Playdate.Network {
|
||||
}
|
||||
}
|
||||
}
|
||||
try Playdate.Network.check(error)
|
||||
try Network.check(error)
|
||||
}
|
||||
|
||||
/// Sends a request with an arbitrary HTTP method.
|
||||
@@ -238,14 +234,14 @@ extension Playdate.Network {
|
||||
}
|
||||
}
|
||||
}
|
||||
try Playdate.Network.check(error)
|
||||
try Network.check(error)
|
||||
}
|
||||
|
||||
// MARK: Response
|
||||
|
||||
/// The last error on the connection, if any.
|
||||
public var error: NetError? {
|
||||
Playdate.Network.optionalError(httpAPI.getError.unsafelyUnwrapped(pointer))
|
||||
Network.optionalError(httpAPI.getError.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The number of bytes read of the current response, and the total
|
||||
@@ -380,7 +376,7 @@ extension Playdate.Network {
|
||||
public static func requestAccess(server: String, port: Int, useSSL: Bool = true,
|
||||
purpose: String? = nil,
|
||||
completion: @escaping (Bool) -> Void) -> AccessReply {
|
||||
Playdate.Network.requestAccess(
|
||||
Network.requestAccess(
|
||||
rawRequest: { tcpAPI.requestAccess.unsafelyUnwrapped($0, $1, $2, $3, $4, $5) },
|
||||
server: server, port: port, useSSL: useSSL, purpose: purpose,
|
||||
completion: completion)
|
||||
@@ -410,7 +406,7 @@ extension Playdate.Network {
|
||||
|
||||
/// The last error on the connection, if any.
|
||||
public var error: NetError? {
|
||||
Playdate.Network.optionalError(tcpAPI.getError.unsafelyUnwrapped(pointer))
|
||||
Network.optionalError(tcpAPI.getError.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// The time to wait for the connection to open, in milliseconds.
|
||||
@@ -425,14 +421,14 @@ extension Playdate.Network {
|
||||
guard let wrapper = TCPConnection.wrapper(for: connection) else { return }
|
||||
let completion = wrapper.openCompletion
|
||||
wrapper.openCompletion = nil
|
||||
completion?(wrapper, Playdate.Network.optionalError(error))
|
||||
completion?(wrapper, Network.optionalError(error))
|
||||
}, nil)
|
||||
try Playdate.Network.check(error)
|
||||
try Network.check(error)
|
||||
}
|
||||
|
||||
/// Closes the connection.
|
||||
public func close() throws(NetError) {
|
||||
try Playdate.Network.check(tcpAPI.close.unsafelyUnwrapped(pointer))
|
||||
try Network.check(tcpAPI.close.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// Called when the connection closes, with the reason if it closed
|
||||
@@ -442,7 +438,7 @@ extension Playdate.Network {
|
||||
if callback != nil {
|
||||
tcpAPI.setConnectionClosedCallback.unsafelyUnwrapped(pointer, { connection, error in
|
||||
guard let wrapper = TCPConnection.wrapper(for: connection) else { return }
|
||||
wrapper.connectionClosedCallback?(wrapper, Playdate.Network.optionalError(error))
|
||||
wrapper.connectionClosedCallback?(wrapper, Network.optionalError(error))
|
||||
})
|
||||
} else {
|
||||
tcpAPI.setConnectionClosedCallback.unsafelyUnwrapped(pointer, nil)
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
public import CPlaydate
|
||||
|
||||
/// The raw C API bootstrap. Everything else in this module (System,
|
||||
/// Graphics, Sprite, Sound, ...) lives at the top level of the `PlayDate`
|
||||
/// module and requires `initialize(with:)` to have been called first.
|
||||
public enum Playdate {
|
||||
/// The raw C API. Populated by `initialize(with:)`.
|
||||
///
|
||||
@@ -29,59 +32,59 @@ public enum Playdate {
|
||||
apiPointer = pointer.assumingMemoryBound(to: PlaydateAPI.self)
|
||||
api = apiPointer.pointee
|
||||
}
|
||||
}
|
||||
|
||||
/// An error reported by the Playdate OS.
|
||||
public struct Error: Swift.Error, Sendable {
|
||||
public let message: String
|
||||
/// An error reported by the Playdate OS.
|
||||
public struct PlaydateError: Swift.Error, Sendable {
|
||||
public let message: String
|
||||
|
||||
init(message: String) {
|
||||
self.message = message
|
||||
}
|
||||
|
||||
init(cString: UnsafePointer<CChar>?) {
|
||||
self.init(message: String(playdateCString: cString) ?? "unknown error")
|
||||
}
|
||||
init(message: String) {
|
||||
self.message = message
|
||||
}
|
||||
|
||||
/// The user's answer to a permission request (microphone, network).
|
||||
public enum AccessReply: UInt32, Sendable {
|
||||
case ask = 0
|
||||
case deny = 1
|
||||
case allow = 2
|
||||
init(cString: UnsafePointer<CChar>?) {
|
||||
self.init(message: String(playdateCString: cString) ?? "unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
/// A Swift view of `PDSystemEvent` with the key code folded into the
|
||||
/// key events.
|
||||
public enum SystemEvent {
|
||||
case initialize
|
||||
case initializeLua
|
||||
case lock
|
||||
case unlock
|
||||
case pause
|
||||
case resume
|
||||
case terminate
|
||||
case keyPressed(keyCode: UInt32)
|
||||
case keyReleased(keyCode: UInt32)
|
||||
case lowPower
|
||||
case mirrorStarted
|
||||
case mirrorEnded
|
||||
/// The user's answer to a permission request (microphone, network).
|
||||
public enum AccessReply: UInt32, Sendable {
|
||||
case ask = 0
|
||||
case deny = 1
|
||||
case allow = 2
|
||||
}
|
||||
|
||||
public init?(event: PDSystemEvent, argument: UInt32) {
|
||||
switch event {
|
||||
case kEventInit: self = .initialize
|
||||
case kEventInitLua: self = .initializeLua
|
||||
case kEventLock: self = .lock
|
||||
case kEventUnlock: self = .unlock
|
||||
case kEventPause: self = .pause
|
||||
case kEventResume: self = .resume
|
||||
case kEventTerminate: self = .terminate
|
||||
case kEventKeyPressed: self = .keyPressed(keyCode: argument)
|
||||
case kEventKeyReleased: self = .keyReleased(keyCode: argument)
|
||||
case kEventLowPower: self = .lowPower
|
||||
case kEventMirrorStarted: self = .mirrorStarted
|
||||
case kEventMirrorEnded: self = .mirrorEnded
|
||||
default: return nil
|
||||
}
|
||||
/// A Swift view of `PDSystemEvent` with the key code folded into the
|
||||
/// key events.
|
||||
public enum SystemEvent {
|
||||
case initialize
|
||||
case initializeLua
|
||||
case lock
|
||||
case unlock
|
||||
case pause
|
||||
case resume
|
||||
case terminate
|
||||
case keyPressed(keyCode: UInt32)
|
||||
case keyReleased(keyCode: UInt32)
|
||||
case lowPower
|
||||
case mirrorStarted
|
||||
case mirrorEnded
|
||||
|
||||
public init?(event: PDSystemEvent, argument: UInt32) {
|
||||
switch event {
|
||||
case kEventInit: self = .initialize
|
||||
case kEventInitLua: self = .initializeLua
|
||||
case kEventLock: self = .lock
|
||||
case kEventUnlock: self = .unlock
|
||||
case kEventPause: self = .pause
|
||||
case kEventResume: self = .resume
|
||||
case kEventTerminate: self = .terminate
|
||||
case kEventKeyPressed: self = .keyPressed(keyCode: argument)
|
||||
case kEventKeyReleased: self = .keyReleased(keyCode: argument)
|
||||
case kEventLowPower: self = .lowPower
|
||||
case kEventMirrorStarted: self = .mirrorStarted
|
||||
case kEventMirrorEnded: self = .mirrorEnded
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ internal import CPlaydate
|
||||
|
||||
private var scoreboardsAPI: playdate_scoreboards { Playdate.api.scoreboards.pointee }
|
||||
|
||||
extension Playdate {
|
||||
/// The scoreboards API for games with online leaderboards.
|
||||
public enum Scoreboards {}
|
||||
}
|
||||
/// The scoreboards API for games with online leaderboards.
|
||||
public enum Scoreboards {}
|
||||
|
||||
extension Playdate.Scoreboards {
|
||||
extension Scoreboards {
|
||||
/// A score on a board.
|
||||
public struct Score {
|
||||
public let rank: UInt32
|
||||
@@ -92,22 +90,22 @@ extension Playdate.Scoreboards {
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated(unsafe) private static var addScoreCompletion: ((Result<Score, Playdate.Error>) -> Void)?
|
||||
nonisolated(unsafe) private static var personalBestCompletion: ((Result<Score, Playdate.Error>) -> Void)?
|
||||
nonisolated(unsafe) private static var boardsCompletion: ((Result<BoardsList, Playdate.Error>) -> Void)?
|
||||
nonisolated(unsafe) private static var scoresCompletion: ((Result<ScoresList, Playdate.Error>) -> Void)?
|
||||
nonisolated(unsafe) private static var addScoreCompletion: ((Result<Score, PlaydateError>) -> Void)?
|
||||
nonisolated(unsafe) private static var personalBestCompletion: ((Result<Score, PlaydateError>) -> Void)?
|
||||
nonisolated(unsafe) private static var boardsCompletion: ((Result<BoardsList, PlaydateError>) -> Void)?
|
||||
nonisolated(unsafe) private static var scoresCompletion: ((Result<ScoresList, PlaydateError>) -> Void)?
|
||||
|
||||
/// Submits a score to the board. Returns `false` if the request could
|
||||
/// not be started.
|
||||
@discardableResult
|
||||
public static func addScore(boardID: String, value: UInt32,
|
||||
completion: @escaping (Result<Score, Playdate.Error>) -> Void) -> Bool {
|
||||
completion: @escaping (Result<Score, PlaydateError>) -> Void) -> Bool {
|
||||
addScoreCompletion = completion
|
||||
return boardID.withPlaydateCString { cBoardID in
|
||||
scoreboardsAPI.addScore.unsafelyUnwrapped(cBoardID, value, { score, errorMessage in
|
||||
let completion = Playdate.Scoreboards.addScoreCompletion
|
||||
Playdate.Scoreboards.addScoreCompletion = nil
|
||||
completion?(Playdate.Scoreboards.result(score, errorMessage))
|
||||
let completion = Scoreboards.addScoreCompletion
|
||||
Scoreboards.addScoreCompletion = nil
|
||||
completion?(Scoreboards.result(score, errorMessage))
|
||||
}) != 0
|
||||
}
|
||||
}
|
||||
@@ -115,26 +113,26 @@ extension Playdate.Scoreboards {
|
||||
/// Fetches the current player's best score on the board.
|
||||
@discardableResult
|
||||
public static func getPersonalBest(boardID: String,
|
||||
completion: @escaping (Result<Score, Playdate.Error>) -> Void) -> Bool {
|
||||
completion: @escaping (Result<Score, PlaydateError>) -> Void) -> Bool {
|
||||
personalBestCompletion = completion
|
||||
return boardID.withPlaydateCString { cBoardID in
|
||||
scoreboardsAPI.getPersonalBest.unsafelyUnwrapped(cBoardID, { score, errorMessage in
|
||||
let completion = Playdate.Scoreboards.personalBestCompletion
|
||||
Playdate.Scoreboards.personalBestCompletion = nil
|
||||
completion?(Playdate.Scoreboards.result(score, errorMessage))
|
||||
let completion = Scoreboards.personalBestCompletion
|
||||
Scoreboards.personalBestCompletion = nil
|
||||
completion?(Scoreboards.result(score, errorMessage))
|
||||
}) != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the list of the game's boards.
|
||||
@discardableResult
|
||||
public static func getScoreboards(completion: @escaping (Result<BoardsList, Playdate.Error>) -> Void) -> Bool {
|
||||
public static func getScoreboards(completion: @escaping (Result<BoardsList, PlaydateError>) -> Void) -> Bool {
|
||||
boardsCompletion = completion
|
||||
return scoreboardsAPI.getScoreboards.unsafelyUnwrapped({ boards, errorMessage in
|
||||
let completion = Playdate.Scoreboards.boardsCompletion
|
||||
Playdate.Scoreboards.boardsCompletion = nil
|
||||
let completion = Scoreboards.boardsCompletion
|
||||
Scoreboards.boardsCompletion = nil
|
||||
guard let boards else {
|
||||
completion?(.failure(Playdate.Error(cString: errorMessage)))
|
||||
completion?(.failure(PlaydateError(cString: errorMessage)))
|
||||
return
|
||||
}
|
||||
let list = BoardsList(boards.pointee)
|
||||
@@ -146,14 +144,14 @@ extension Playdate.Scoreboards {
|
||||
/// Fetches the scores on the board.
|
||||
@discardableResult
|
||||
public static func getScores(boardID: String,
|
||||
completion: @escaping (Result<ScoresList, Playdate.Error>) -> Void) -> Bool {
|
||||
completion: @escaping (Result<ScoresList, PlaydateError>) -> Void) -> Bool {
|
||||
scoresCompletion = completion
|
||||
return boardID.withPlaydateCString { cBoardID in
|
||||
scoreboardsAPI.getScores.unsafelyUnwrapped(cBoardID, { scores, errorMessage in
|
||||
let completion = Playdate.Scoreboards.scoresCompletion
|
||||
Playdate.Scoreboards.scoresCompletion = nil
|
||||
let completion = Scoreboards.scoresCompletion
|
||||
Scoreboards.scoresCompletion = nil
|
||||
guard let scores else {
|
||||
completion?(.failure(Playdate.Error(cString: errorMessage)))
|
||||
completion?(.failure(PlaydateError(cString: errorMessage)))
|
||||
return
|
||||
}
|
||||
let list = ScoresList(scores.pointee)
|
||||
@@ -164,9 +162,9 @@ extension Playdate.Scoreboards {
|
||||
}
|
||||
|
||||
private static func result(_ score: UnsafeMutablePointer<PDScore>?,
|
||||
_ errorMessage: UnsafePointer<CChar>?) -> Result<Score, Playdate.Error> {
|
||||
_ errorMessage: UnsafePointer<CChar>?) -> Result<Score, PlaydateError> {
|
||||
guard let score else {
|
||||
return .failure(Playdate.Error(cString: errorMessage))
|
||||
return .failure(PlaydateError(cString: errorMessage))
|
||||
}
|
||||
let value = Score(score.pointee)
|
||||
scoreboardsAPI.freeScore.unsafelyUnwrapped(score)
|
||||
|
||||
@@ -9,12 +9,10 @@ internal import CPlaydate
|
||||
|
||||
var snd: playdate_sound { Playdate.api.sound.pointee }
|
||||
|
||||
extension Playdate {
|
||||
/// The sound API: channels, players, synths, sequences, and effects.
|
||||
public enum Sound {}
|
||||
}
|
||||
/// The sound API: channels, players, synths, sequences, and effects.
|
||||
public enum Sound {}
|
||||
|
||||
extension Playdate.Sound {
|
||||
extension Sound {
|
||||
/// A note as a MIDI note number, where 60 is middle C. Fractional values
|
||||
/// are valid.
|
||||
public typealias MIDINote = Float
|
||||
@@ -60,12 +58,9 @@ extension Playdate.Sound {
|
||||
case headset = 2
|
||||
}
|
||||
|
||||
/// The user's answer to a permission request.
|
||||
public typealias AccessReply = Playdate.AccessReply
|
||||
|
||||
/// The most recent sound error as a thrown error.
|
||||
static func lastError() -> Playdate.Error {
|
||||
Playdate.Error(cString: snd.getError.unsafelyUnwrapped())
|
||||
static func lastError() -> PlaydateError {
|
||||
PlaydateError(cString: snd.getError.unsafelyUnwrapped())
|
||||
}
|
||||
|
||||
// MARK: - Top-level functions
|
||||
@@ -96,7 +91,7 @@ extension Playdate.Sound {
|
||||
if callback != nil {
|
||||
return snd.setMicCallback.unsafelyUnwrapped({ _, buffer, length in
|
||||
let samples = UnsafeMutableBufferPointer(start: buffer, count: Int(length))
|
||||
return Playdate.Sound.micCallback?(samples) == true ? 1 : 0
|
||||
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
|
||||
@@ -147,7 +142,7 @@ extension Playdate.Sound {
|
||||
headphoneChangeCallback = callback
|
||||
if callback != nil {
|
||||
snd.getHeadphoneState.unsafelyUnwrapped(nil, nil, { headphone, mic in
|
||||
Playdate.Sound.headphoneChangeCallback?(headphone != 0, mic != 0)
|
||||
Sound.headphoneChangeCallback?(headphone != 0, mic != 0)
|
||||
})
|
||||
} else {
|
||||
snd.getHeadphoneState.unsafelyUnwrapped(nil, nil, nil)
|
||||
|
||||
@@ -8,7 +8,7 @@ internal import CPlaydate
|
||||
|
||||
private var effectAPI: playdate_sound_effect { snd.effect.pointee }
|
||||
|
||||
extension Playdate.Sound {
|
||||
extension Sound {
|
||||
/// An effect that processes a channel's audio: the base class of the
|
||||
/// built-in effects. Wraps `SoundEffect`.
|
||||
public class Effect {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate.Sound {
|
||||
extension Sound {
|
||||
/// A value that can modulate a parameter. The base class of `Signal`,
|
||||
/// `LFO`, `Envelope`, and `ControlSignal`. Wraps `PDSynthSignalValue`.
|
||||
public class SignalValue {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate.Sound {
|
||||
extension Sound {
|
||||
/// A source of audio: the base class of `FilePlayer`, `SamplePlayer`,
|
||||
/// `Synth`, `DelayLineTap`, and `CallbackSource`. Wraps `SoundSource`.
|
||||
public class Source {
|
||||
@@ -116,7 +116,7 @@ extension Playdate.Sound {
|
||||
}
|
||||
|
||||
/// Creates a player and loads the audio file at `path`.
|
||||
public convenience init(path: String) throws(Playdate.Error) {
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
self.init()
|
||||
try load(path: path)
|
||||
}
|
||||
@@ -128,12 +128,12 @@ extension Playdate.Sound {
|
||||
}
|
||||
|
||||
/// Prepares the player to stream the file at `path`.
|
||||
public func load(path: String) throws(Playdate.Error) {
|
||||
public func load(path: String) throws(PlaydateError) {
|
||||
let loaded = path.withPlaydateCString {
|
||||
FilePlayer.api.loadIntoPlayer.unsafelyUnwrapped(pointer, $0) != 0
|
||||
}
|
||||
if !loaded {
|
||||
throw Playdate.Error(message: "unable to load audio file: \(path)")
|
||||
throw PlaydateError(message: "unable to load audio file: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,10 +266,10 @@ extension Playdate.Sound {
|
||||
}
|
||||
|
||||
/// Loads the wav or aiff file at `path`.
|
||||
public convenience init(path: String) throws(Playdate.Error) {
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
let pointer = path.withPlaydateCString { AudioSample.api.load.unsafelyUnwrapped($0) }
|
||||
guard let pointer else {
|
||||
throw Playdate.Error(message: "unable to load sample: \(path)")
|
||||
throw PlaydateError(message: "unable to load sample: \(path)")
|
||||
}
|
||||
self.init(pointer: pointer, isOwned: true)
|
||||
}
|
||||
@@ -294,12 +294,12 @@ extension Playdate.Sound {
|
||||
}
|
||||
|
||||
/// Loads the file at `path` into this sample's buffer.
|
||||
public func load(path: String) throws(Playdate.Error) {
|
||||
public func load(path: String) throws(PlaydateError) {
|
||||
let loaded = path.withPlaydateCString {
|
||||
AudioSample.api.loadIntoSample.unsafelyUnwrapped(pointer, $0) != 0
|
||||
}
|
||||
if !loaded {
|
||||
throw Playdate.Error(message: "unable to load sample: \(path)")
|
||||
throw PlaydateError(message: "unable to load sample: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ extension Playdate.Sound {
|
||||
}
|
||||
|
||||
/// Creates a player for the sample at `path`.
|
||||
public convenience init(path: String) throws(Playdate.Error) {
|
||||
public convenience init(path: String) throws(PlaydateError) {
|
||||
self.init()
|
||||
sample = try AudioSample(path: path)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate.Sound {
|
||||
extension Sound {
|
||||
/// A synthesizer voice. Wraps `PDSynth`.
|
||||
public final class Synth: Source {
|
||||
private static var api: playdate_sound_synth { snd.synth.pointee }
|
||||
@@ -109,11 +109,11 @@ extension Playdate.Sound {
|
||||
/// Uses a wavetable for the synth. `log2size` is the base-2 log of
|
||||
/// each waveform's size (e.g. 8 for 256 samples).
|
||||
public func setWavetable(_ sample: AudioSample, log2size: Int,
|
||||
columns: Int, rows: Int) throws(Playdate.Error) {
|
||||
columns: Int, rows: Int) throws(PlaydateError) {
|
||||
retainedSample = sample
|
||||
guard Synth.api.setWavetable.unsafelyUnwrapped(
|
||||
pointer, sample.pointer, Int32(log2size), Int32(columns), Int32(rows)) != 0 else {
|
||||
throw Playdate.Error(message: "invalid wavetable dimensions")
|
||||
throw PlaydateError(message: "invalid wavetable dimensions")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ extension Playdate.Sound {
|
||||
}
|
||||
|
||||
/// Creates a sequence and loads the MIDI file at `path`.
|
||||
public convenience init(midiFilePath: String) throws(Playdate.Error) {
|
||||
public convenience init(midiFilePath: String) throws(PlaydateError) {
|
||||
self.init()
|
||||
try loadMIDIFile(path: midiFilePath)
|
||||
}
|
||||
@@ -506,12 +506,12 @@ extension Playdate.Sound {
|
||||
Sequence.api.freeSequence.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
public func loadMIDIFile(path: String) throws(Playdate.Error) {
|
||||
public func loadMIDIFile(path: String) throws(PlaydateError) {
|
||||
let loaded = path.withPlaydateCString {
|
||||
Sequence.api.loadMIDIFile.unsafelyUnwrapped(pointer, $0) != 0
|
||||
}
|
||||
if !loaded {
|
||||
throw Playdate.Error(message: "unable to load MIDI file: \(path)")
|
||||
throw PlaydateError(message: "unable to load MIDI file: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+500
-504
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,10 @@
|
||||
|
||||
internal import CPlaydate
|
||||
|
||||
extension Playdate {
|
||||
/// The system API: logging, input, time, menu items, and device state.
|
||||
public enum System {}
|
||||
}
|
||||
/// The system API: logging, input, time, menu items, and device state.
|
||||
public enum System {}
|
||||
|
||||
extension Playdate.System {
|
||||
extension System {
|
||||
private static var api: playdate_sys { Playdate.api.system.pointee }
|
||||
|
||||
// MARK: - Types
|
||||
@@ -189,8 +187,8 @@ extension Playdate.System {
|
||||
public static func getServerTime(_ completion: @escaping (_ time: String?, _ error: String?) -> Void) {
|
||||
serverTimeCompletion = completion
|
||||
api.getServerTime.unsafelyUnwrapped { time, error in
|
||||
let completion = Playdate.System.serverTimeCompletion
|
||||
Playdate.System.serverTimeCompletion = nil
|
||||
let completion = System.serverTimeCompletion
|
||||
System.serverTimeCompletion = nil
|
||||
completion?(String(playdateCString: time), String(playdateCString: error))
|
||||
}
|
||||
}
|
||||
@@ -203,7 +201,7 @@ extension Playdate.System {
|
||||
public static func setUpdateCallback(_ callback: @escaping () -> Bool) {
|
||||
updateCallback = callback
|
||||
api.setUpdateCallback.unsafelyUnwrapped({ _ in
|
||||
Playdate.System.updateCallback?() == true ? 1 : 0
|
||||
System.updateCallback?() == true ? 1 : 0
|
||||
}, nil)
|
||||
}
|
||||
|
||||
@@ -231,7 +229,7 @@ extension Playdate.System {
|
||||
buttonCallback = callback
|
||||
if callback != nil {
|
||||
api.setButtonCallback.unsafelyUnwrapped({ button, down, when, _ in
|
||||
Playdate.System.buttonCallback?(Buttons(button), down != 0, when) ?? 0
|
||||
System.buttonCallback?(Buttons(button), down != 0, when) ?? 0
|
||||
}, nil, Int32(queueSize))
|
||||
} else {
|
||||
api.setButtonCallback.unsafelyUnwrapped(nil, nil, Int32(queueSize))
|
||||
@@ -280,7 +278,7 @@ extension Playdate.System {
|
||||
if callback != nil {
|
||||
api.setSerialMessageCallback.unsafelyUnwrapped { data in
|
||||
guard let message = String(playdateCString: data) else { return }
|
||||
Playdate.System.serialMessageCallback?(message)
|
||||
System.serialMessageCallback?(message)
|
||||
}
|
||||
} else {
|
||||
api.setSerialMessageCallback.unsafelyUnwrapped(nil)
|
||||
@@ -415,7 +413,7 @@ extension Playdate.System {
|
||||
|
||||
/// Sets a custom image for the pause menu, optionally shifted left by
|
||||
/// `xOffset` (0...200).
|
||||
public static func setMenuImage(_ bitmap: Playdate.Graphics.Bitmap?, xOffset: Int = 0) {
|
||||
public static func setMenuImage(_ bitmap: Graphics.Bitmap?, xOffset: Int = 0) {
|
||||
api.setMenuImage.unsafelyUnwrapped(bitmap?.pointer, Int32(xOffset))
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,20 @@ import Testing
|
||||
// inline C helpers that work without it.
|
||||
|
||||
@Test func buttonsOptionSetMatchesCMasks() {
|
||||
#expect(Playdate.System.Buttons.left.rawValue == 1 << 0)
|
||||
#expect(Playdate.System.Buttons.right.rawValue == 1 << 1)
|
||||
#expect(Playdate.System.Buttons.up.rawValue == 1 << 2)
|
||||
#expect(Playdate.System.Buttons.down.rawValue == 1 << 3)
|
||||
#expect(Playdate.System.Buttons.b.rawValue == 1 << 4)
|
||||
#expect(Playdate.System.Buttons.a.rawValue == 1 << 5)
|
||||
#expect(System.Buttons.left.rawValue == 1 << 0)
|
||||
#expect(System.Buttons.right.rawValue == 1 << 1)
|
||||
#expect(System.Buttons.up.rawValue == 1 << 2)
|
||||
#expect(System.Buttons.down.rawValue == 1 << 3)
|
||||
#expect(System.Buttons.b.rawValue == 1 << 4)
|
||||
#expect(System.Buttons.a.rawValue == 1 << 5)
|
||||
|
||||
let combined: Playdate.System.Buttons = [.a, .up]
|
||||
let combined: System.Buttons = [.a, .up]
|
||||
#expect(combined.contains(.a))
|
||||
#expect(!combined.contains(.b))
|
||||
}
|
||||
|
||||
@Test func graphicsRectConvertsBetweenOriginSizeAndEdges() {
|
||||
let rect = Playdate.Graphics.Rect(x: 10, y: 20, width: 30, height: 40)
|
||||
let rect = Graphics.Rect(x: 10, y: 20, width: 30, height: 40)
|
||||
#expect(rect.left == 10)
|
||||
#expect(rect.right == 40)
|
||||
#expect(rect.top == 20)
|
||||
@@ -29,13 +29,13 @@ import Testing
|
||||
#expect(translated.left == 15)
|
||||
#expect(translated.top == 15)
|
||||
|
||||
let roundTripped = Playdate.Graphics.Rect(rect.cValue)
|
||||
let roundTripped = Graphics.Rect(rect.cValue)
|
||||
#expect(roundTripped.left == rect.left && roundTripped.bottom == rect.bottom)
|
||||
}
|
||||
|
||||
@Test func spriteRectRoundTripsThroughC() {
|
||||
let rect = Playdate.Rect(x: 1.5, y: 2.5, width: 3, height: 4)
|
||||
let roundTripped = Playdate.Rect(rect.cValue)
|
||||
let rect = Rect(x: 1.5, y: 2.5, width: 3, height: 4)
|
||||
let roundTripped = Rect(rect.cValue)
|
||||
#expect(roundTripped.x == 1.5)
|
||||
#expect(roundTripped.y == 2.5)
|
||||
#expect(roundTripped.width == 3)
|
||||
@@ -43,23 +43,23 @@ import Testing
|
||||
}
|
||||
|
||||
@Test func soundFormatPropertiesMatchCMacros() {
|
||||
#expect(!Playdate.Sound.Format.mono8bit.isStereo)
|
||||
#expect(Playdate.Sound.Format.stereo16bit.isStereo)
|
||||
#expect(Playdate.Sound.Format.mono16bit.is16bit)
|
||||
#expect(!Playdate.Sound.Format.monoADPCM.is16bit)
|
||||
#expect(!Sound.Format.mono8bit.isStereo)
|
||||
#expect(Sound.Format.stereo16bit.isStereo)
|
||||
#expect(Sound.Format.mono16bit.is16bit)
|
||||
#expect(!Sound.Format.monoADPCM.is16bit)
|
||||
|
||||
#expect(Playdate.Sound.Format.mono8bit.bytesPerFrame == 1)
|
||||
#expect(Playdate.Sound.Format.stereo8bit.bytesPerFrame == 2)
|
||||
#expect(Playdate.Sound.Format.mono16bit.bytesPerFrame == 2)
|
||||
#expect(Playdate.Sound.Format.stereo16bit.bytesPerFrame == 4)
|
||||
#expect(Sound.Format.mono8bit.bytesPerFrame == 1)
|
||||
#expect(Sound.Format.stereo8bit.bytesPerFrame == 2)
|
||||
#expect(Sound.Format.mono16bit.bytesPerFrame == 2)
|
||||
#expect(Sound.Format.stereo16bit.bytesPerFrame == 4)
|
||||
}
|
||||
|
||||
@Test func midiNoteFrequencyConversionRoundTrips() {
|
||||
// A4 (MIDI 69) is 440 Hz.
|
||||
let a4 = Playdate.Sound.frequency(forNote: 69)
|
||||
let a4 = Sound.frequency(forNote: 69)
|
||||
#expect(abs(a4 - 440) < 0.01)
|
||||
|
||||
let note = Playdate.Sound.note(forFrequency: 440)
|
||||
let note = Sound.note(forFrequency: 440)
|
||||
#expect(abs(note - 69) < 0.001)
|
||||
}
|
||||
|
||||
@@ -74,9 +74,9 @@ import Testing
|
||||
}
|
||||
|
||||
@Test func dateTimeMirrorsCStruct() {
|
||||
let dateTime = Playdate.System.DateTime(year: 2026, month: 7, day: 24, weekday: 5,
|
||||
let dateTime = System.DateTime(year: 2026, month: 7, day: 24, weekday: 5,
|
||||
hour: 12, minute: 34, second: 56)
|
||||
let roundTripped = Playdate.System.DateTime(dateTime.cValue)
|
||||
let roundTripped = System.DateTime(dateTime.cValue)
|
||||
#expect(roundTripped.year == 2026)
|
||||
#expect(roundTripped.month == 7)
|
||||
#expect(roundTripped.day == 24)
|
||||
|
||||
Reference in New Issue
Block a user