From 8cf0715cdc03df039b4b4a5b5115c34b1f533b7a Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Fri, 24 Jul 2026 11:29:13 +0200 Subject: [PATCH] Add DocC documentation and a runnable example game - DocC catalog (Sources/PlayDate/PlayDate.docc) with a curated landing page and a Getting Started article; swift-docc-plugin wired up for `swift package generate-documentation`, and a GitHub Pages deployment workflow renders docs on every push to main. - Examples/HelloPlaydate: a minimal game (bouncing box, crank needle, button handling, system menu item) whose build.sh compiles the game as a dylib and produces a runnable simulator .pdx via the SDK's pdc. Verified: the pdx builds and exports the eventHandler entry point. Co-Authored-By: Claude Fable 5 --- .github/workflows/docs.yml | 66 ++++++++++ .gitignore | 5 +- Examples/HelloPlaydate/Package.swift | 20 +++ Examples/HelloPlaydate/README.md | 23 ++++ Examples/HelloPlaydate/Source/pdxinfo | 6 + .../Sources/HelloPlaydate/Game.swift | 97 ++++++++++++++ Examples/HelloPlaydate/build.sh | 21 +++ Package.swift | 4 + README.md | 120 +++++++----------- .../PlayDate/PlayDate.docc/GettingStarted.md | 66 ++++++++++ Sources/PlayDate/PlayDate.docc/PlayDate.md | 61 +++++++++ 11 files changed, 417 insertions(+), 72 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 Examples/HelloPlaydate/Package.swift create mode 100644 Examples/HelloPlaydate/README.md create mode 100644 Examples/HelloPlaydate/Source/pdxinfo create mode 100644 Examples/HelloPlaydate/Sources/HelloPlaydate/Game.swift create mode 100755 Examples/HelloPlaydate/build.sh create mode 100644 Sources/PlayDate/PlayDate.docc/GettingStarted.md create mode 100644 Sources/PlayDate/PlayDate.docc/PlayDate.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..ff2bfba --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,66 @@ +name: Documentation + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: docs + cancel-in-progress: true + +env: + PLAYDATE_SDK_VERSION: "3.1.1" + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest + + - name: Cache Playdate SDK + id: sdk-cache + uses: actions/cache@v4 + with: + path: ~/PlaydateSDK + key: playdate-sdk-${{ env.PLAYDATE_SDK_VERSION }} + + - name: Download Playdate SDK headers + if: steps.sdk-cache.outputs.cache-hit != 'true' + run: | + curl -sL "https://download.panic.com/playdate_sdk/Linux/PlaydateSDK-${PLAYDATE_SDK_VERSION}.tar.gz" | tar xz -C "$HOME" + mv "$HOME/PlaydateSDK-${PLAYDATE_SDK_VERSION}" "$HOME/PlaydateSDK" + test -f "$HOME/PlaydateSDK/C_API/pd_api.h" + + - name: Install pkg-config module + run: | + PLAYDATE_SDK_PATH="$HOME/PlaydateSDK" Scripts/install-pkgconfig.sh "$HOME/pkgconfig" + echo "PKG_CONFIG_PATH=$HOME/pkgconfig" >> "$GITHUB_ENV" + + - name: Generate documentation + run: | + swift package --allow-writing-to-directory docs \ + generate-documentation --target PlayDate \ + --disable-indexing \ + --transform-for-static-hosting \ + --hosting-base-path play-date \ + --output-path docs + + - uses: actions/upload-pages-artifact@v3 + with: + path: docs + + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 0023a53..cb83c06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .DS_Store -/.build +.build/ /Packages +# Example build products +Examples/*/Source/pdex.dylib +*.pdx xcuserdata/ DerivedData/ .swiftpm/configuration/registries.json diff --git a/Examples/HelloPlaydate/Package.swift b/Examples/HelloPlaydate/Package.swift new file mode 100644 index 0000000..0f12bed --- /dev/null +++ b/Examples/HelloPlaydate/Package.swift @@ -0,0 +1,20 @@ +// swift-tools-version: 6.4 + +import PackageDescription + +let package = Package( + name: "HelloPlaydate", + products: [ + // The Playdate Simulator loads the game as pdex.dylib. + .library(name: "pdex", type: .dynamic, targets: ["HelloPlaydate"]), + ], + dependencies: [ + .package(path: "../.."), + ], + targets: [ + .target( + name: "HelloPlaydate", + dependencies: [.product(name: "PlayDate", package: "play-date")] + ), + ] +) diff --git a/Examples/HelloPlaydate/README.md b/Examples/HelloPlaydate/README.md new file mode 100644 index 0000000..1f72997 --- /dev/null +++ b/Examples/HelloPlaydate/README.md @@ -0,0 +1,23 @@ +# Hello Playdate + +A minimal game built on the `play-date` bindings: a bouncing box, a +crank-aimed needle, button handling, a system menu item, and an FPS counter. + +## Build and run (Playdate Simulator) + +With the Playdate SDK installed (and the one-time +`Scripts/install-pkgconfig.sh` setup from the repository root done): + +```sh +./build.sh +open -a "$HOME/Developer/PlaydateSDK/bin/Playdate Simulator.app" HelloPlaydate.pdx +``` + +The script compiles the game as a dylib, places it in `Source/` next to +`pdxinfo`, and runs the SDK's `pdc` to produce `HelloPlaydate.pdx`. + +## Device builds + +Running on hardware requires the Embedded Swift + ARM toolchain pipeline +(see the repository README and Apple's swift-playdate-examples for the +Makefile setup). This example covers the simulator workflow only. diff --git a/Examples/HelloPlaydate/Source/pdxinfo b/Examples/HelloPlaydate/Source/pdxinfo new file mode 100644 index 0000000..d5dacd4 --- /dev/null +++ b/Examples/HelloPlaydate/Source/pdxinfo @@ -0,0 +1,6 @@ +name=Hello Playdate +author=Röck+Cöde +description=Minimal example for the play-date Swift bindings +bundleID=com.rock-n-code.hello-playdate +version=0.1 +buildNumber=1 diff --git a/Examples/HelloPlaydate/Sources/HelloPlaydate/Game.swift b/Examples/HelloPlaydate/Sources/HelloPlaydate/Game.swift new file mode 100644 index 0000000..d0ee22d --- /dev/null +++ b/Examples/HelloPlaydate/Sources/HelloPlaydate/Game.swift @@ -0,0 +1,97 @@ +// +// Game.swift +// A minimal Playdate game built on the play-date Swift bindings: a +// bouncing box, a crank-aimed needle, button logging, and a system menu +// item. +// + +import CPlaydate +import PlayDate + +@_cdecl("eventHandler") +public func eventHandler( + pointer: UnsafeMutableRawPointer, + event: PDSystemEvent, + argument: UInt32 +) -> Int32 { + if case .initialize = SystemEvent(event: event, argument: argument) { + Playdate.initialize(with: pointer) // must happen before anything else + Game.shared.start() + } + + return 0 +} + +final class Game { + nonisolated(unsafe) static let shared = Game() + + private var x: Float = 200 + private var y: Float = 120 + private var dx: Float = 3 + private var dy: Float = 2 + private let boxSize = 24 + + func start() { + Display.setRefreshRate(50) + + System.addMenuItem(title: "reset") { _ in + Game.shared.reset() + } + + System.setUpdateCallback { + Game.shared.update() + return true // redraw the display this frame + } + } + + private func reset() { + x = 200 + y = 120 + } + + private func update() { + moveBox() + handleInput() + draw() + } + + private func moveBox() { + let width = Float(Display.width) + let height = Float(Display.height) + + x += dx + y += dy + if x < 0 || x > width - Float(boxSize) { dx = -dx } + if y < 24 || y > height - Float(boxSize) { dy = -dy } + } + + private func handleInput() { + let (_, pushed, _) = System.buttonState + if pushed.contains(.a) { + System.log("A pressed at \(System.currentTimeMilliseconds)ms") + } + if pushed.contains(.b) { + (dx, dy) = (-dx, -dy) + } + } + + private func draw() { + Graphics.clear(color: .white) + Graphics.drawText("Hëllo from Swift — Ⓑ reverses", x: 8, y: 4) + Graphics.fillRect(x: Int(x), y: Int(y), width: boxSize, height: boxSize, color: .black) + + if !System.isCrankDocked { + // A needle from the screen center pointing where the crank points. + let radians = System.crankAngle * .pi / 180 + let centerX = Display.width / 2 + let centerY = Display.height / 2 + Graphics.drawLine( + x1: centerX, y1: centerY, + x2: centerX + Int(40 * sinf(radians)), + y2: centerY - Int(40 * cosf(radians)), + width: 2, color: .xor) + } + + System.drawFPS(x: 380, y: 4) + } +} diff --git a/Examples/HelloPlaydate/build.sh b/Examples/HelloPlaydate/build.sh new file mode 100755 index 0000000..f95ed9f --- /dev/null +++ b/Examples/HelloPlaydate/build.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Builds HelloPlaydate.pdx for the Playdate Simulator: compiles the game as +# a dylib, places it in the pdc source folder, and runs the SDK's pdc. +# +# Device builds need the Embedded Swift + ARM toolchain setup described in +# the repository README; this script covers the simulator only. + +set -eu +cd "$(dirname "$0")" + +sdk_path="${PLAYDATE_SDK_PATH:-$HOME/Developer/PlaydateSDK}" + +swift build -c release +bin_path="$(swift build -c release --show-bin-path)" +cp "$bin_path/libpdex.dylib" Source/pdex.dylib + +"$sdk_path/bin/pdc" Source HelloPlaydate.pdx + +echo "Built HelloPlaydate.pdx — run it with:" +echo " open -a \"$sdk_path/bin/Playdate Simulator.app\" HelloPlaydate.pdx" diff --git a/Package.swift b/Package.swift index c650c2f..6ee2de4 100644 --- a/Package.swift +++ b/Package.swift @@ -10,6 +10,10 @@ let package = Package( targets: ["PlayDate"] ), ], + dependencies: [ + // Documentation generation only; not linked into the library. + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.0"), + ], targets: [ // The Playdate C API headers, resolved through the "playdate" // pkg-config module. Run Scripts/install-pkgconfig.sh once to point diff --git a/README.md b/README.md index 4fda821..69d903c 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,7 @@ Swift bindings to the [Playdate](https://play.date) C API. -The Playdate C API is delivered as a `PlaydateAPI*` struct of function -pointers that the firmware hands to your game at launch. This package wraps -that surface in idiomatic Swift: namespaced APIs, wrapper types with -ownership semantics, closures instead of function-pointer/userdata pairs, -`OptionSet`s and `enum`s instead of raw constants, and typed `throws` for -fallible calls. +The Playdate C API is delivered as a `PlaydateAPI*` struct of function pointers that the firmware hands to your game at launch. This package wraps that surface in idiomatic Swift: namespaced APIs, wrapper types with ownership semantics, closures instead of function-pointer/userdata pairs, `OptionSet`s and `enum`s instead of raw constants, and typed `throws` for fallible calls. All ten C subsystems are covered: @@ -28,28 +23,20 @@ All ten C subsystems are covered: ## Requirements -- The [Playdate SDK](https://play.date/dev/) (3.1.1 or later). The SDK is - not vendored into this repository. +- The [Playdate SDK](https://play.date/dev/) (3.1.1 or later). The SDK is not vendored into this repository. - Swift 6.4 tools or later. ### One-time SDK setup -The `CPlaydate` target resolves `pd_api.h` through a `playdate` pkg-config -module, so the package works as a normal SwiftPM dependency without unsafe -build flags. Generate the pkg-config file once per machine: +The `CPlaydate` target resolves `pd_api.h` through a `playdate` pkg-config module, so the package works as a normal SwiftPM dependency without unsafe build flags. Generate the pkg-config file once per machine: ```sh Scripts/install-pkgconfig.sh ``` -The script locates the SDK via `PLAYDATE_SDK_PATH` (default -`~/Developer/PlaydateSDK`) and writes `playdate.pc` into a directory SwiftPM -searches by default (`/usr/local/lib/pkgconfig`). Pass a custom destination -as an argument if you prefer another location on your `PKG_CONFIG_PATH`. +The script locates the SDK via `PLAYDATE_SDK_PATH` (default `~/Developer/PlaydateSDK`) and writes `playdate.pc` into a directory SwiftPM searches by default (`/usr/local/lib/pkgconfig`). Pass a custom destination as an argument if you prefer another location on your `PKG_CONFIG_PATH`. -If Xcode had the package open before you ran the script, make it re-read the -manifest (File ▸ Packages ▸ Reset Package Caches) — Xcode caches package -resolution and won't notice the new `.pc` file on its own. +If Xcode had the package open before you ran the script, make it re-read the manifest (File ▸ Packages ▸ Reset Package Caches) — Xcode caches package resolution and won't notice the new `.pc` file on its own. ## Adding the dependency @@ -70,18 +57,18 @@ targets: [ ## Getting started -A Playdate game has a single C entry point, `eventHandler`. Export it with -`@_cdecl`, initialize the binding on the first event, and install an update -callback: +A Playdate game has a single C entry point, `eventHandler`. Export it with `@_cdecl`, initialize the binding on the first event, and install an update callback: ```swift import CPlaydate import PlayDate @_cdecl("eventHandler") -func eventHandler(pointer: UnsafeMutableRawPointer, - event: PDSystemEvent, - argument: UInt32) -> Int32 { +func eventHandler( + pointer: UnsafeMutableRawPointer, + event: PDSystemEvent, + argument: UInt32 +) -> Int32 { switch SystemEvent(event: event, argument: argument) { case .initialize: Playdate.initialize(with: pointer) // must happen before anything else @@ -91,6 +78,7 @@ func eventHandler(pointer: UnsafeMutableRawPointer, default: break } + return 0 } @@ -120,9 +108,7 @@ final class Game { } ``` -`Playdate.initialize(with:)` stores the API pointer once; every wrapper in -the module uses it from then on. Calling any wrapper before `initialize` is -a programmer error and will crash. +`Playdate.initialize(with:)` stores the API pointer once; every wrapper in the module uses it from then on. Calling any wrapper before `initialize` is a programmer error and will crash. ## Tour of the API @@ -202,10 +188,7 @@ for collision in collisions where collision.other.tag == Tags.brick { } ``` -Sprite callbacks (`setUpdateFunction`, `setDrawFunction`, -`setCollisionResponseFunction`) receive the Swift wrapper back. The C-level -sprite userdata slot is reserved by the binding for that recovery — use the -`userdata` property on `Sprite` for your own per-sprite storage instead. +Sprite callbacks (`setUpdateFunction`, `setDrawFunction`, `setCollisionResponseFunction`) receive the Swift wrapper back. The C-level sprite userdata slot is reserved by the binding for that recovery — use the `userdata` property on `Sprite` for your own per-sprite storage instead. ### Sound @@ -295,61 +278,56 @@ try Lua.addFunction(double, name: "mylib.double") ## Conventions -- **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 - OS-owned objects (a `Bitmap` from a `BitmapTable`, a track from a - `Sequence`, …) don't free them — keep the owner alive instead, as - documented on each API. Resources a C object keeps referencing (a sprite's - image, a synth's sample, modulators, menu-item option titles) are retained - by the wrapper automatically. -- **Callbacks.** Where the C API provides a userdata slot, closures are - supported everywhere and delivered back with the right wrapper. A few C - callbacks have no userdata (serial messages, headphone changes, scoreboard - completions, `getServerTime`); those track one Swift closure at a time, as - noted in their documentation. -- **Threading.** The Playdate runtime is single-threaded (audio callbacks - excepted); statics in the binding are `nonisolated(unsafe)` on that basis. - Don't call the API from other threads. +- **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 OS-owned objects (a `Bitmap` from a `BitmapTable`, a track from a `Sequence`, …) don't free them — keep the owner alive instead, as documented on each API. Resources a C object keeps referencing (a sprite's image, a synth's sample, modulators, menu-item option titles) are retained by the wrapper automatically. +- **Callbacks.** Where the C API provides a userdata slot, closures are supported everywhere and delivered back with the right wrapper. A few C callbacks have no userdata (serial messages, headphone changes, scoreboard + completions, `getServerTime`); those track one Swift closure at a time, as noted in their documentation. +- **Threading.** The Playdate runtime is single-threaded (audio callbacks excepted); statics in the binding are `nonisolated(unsafe)` on that basis. Don't call the API from other threads. ## Building for the simulator and device -This package builds as a plain Swift library, which is how you develop and -unit-test game logic on the host (`swift test` works out of the box). +This package builds as a plain Swift library, which is how you develop and unit-test game logic on the host (`swift test` works out of the box). Shipping a `.pdx` needs the Playdate toolchain on top: - **Simulator** builds compile your game as a host dylib placed in the pdx. -- **Device** builds require Embedded Swift for ARM Cortex-M7 - (`-enable-experimental-feature Embedded`, triple `armv7em-none-none-eabi`). +- **Device** builds require Embedded Swift for ARM Cortex-M7 (`-enable-experimental-feature Embedded`, triple `armv7em-none-none-eabi`). -The wrappers are written within the Embedded Swift subset for exactly this -reason: no Foundation, no reflection, no untyped throws. That claim is -enforced, not aspirational: `Scripts/build-embedded.sh` cross-compiles the -whole module for `armv7em-none-none-eabi` with Embedded Swift enabled, and -CI runs it on every push. Running it locally needs a swift.org development -snapshot toolchain (Xcode's toolchain doesn't ship the bare-metal embedded -stdlib) and the Arm GNU toolchain headers: +The wrappers are written within the Embedded Swift subset for exactly this reason: no Foundation, no reflection, no untyped throws. That claim is enforced, not aspirational: `Scripts/build-embedded.sh` cross-compiles the whole module for `armv7em-none-none-eabi` with Embedded Swift enabled, and CI runs it on every push. Running it locally needs a swift.org development snapshot toolchain (Xcode's toolchain doesn't ship the bare-metal embedded stdlib) and the Arm GNU toolchain headers: ```sh SWIFT_BIN=~/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-.xctoolchain/usr/bin/swift \ Scripts/build-embedded.sh ``` -See Apple's -[swift-playdate-examples](https://github.com/apple/swift-playdate-examples) -for a working Makefile/toolchain setup that this library slots into. +See Apple's [swift-playdate-examples](https://github.com/apple/swift-playdate-examples) for a working Makefile/toolchain setup that this library slots into. + +## Example + +[`Examples/HelloPlaydate`](Examples/HelloPlaydate) is a complete minimal game — bouncing box, crank needle, button handling, a system menu item — that builds into a runnable simulator `.pdx`: + +```sh +cd Examples/HelloPlaydate +./build.sh +open -a "$HOME/Developer/PlaydateSDK/bin/Playdate Simulator.app" HelloPlaydate.pdx +``` + +## Documentation + +The API reference is a DocC catalog. Generate it locally with: + +```sh +swift package generate-documentation --target PlayDate +``` + +or browse it with Xcode's documentation viewer (Product ▸ Build Documentation). Pushes to `main` publish the rendered docs to GitHub Pages via `.github/workflows/docs.yml` (enable Pages ▸ Source: GitHub Actions in the repository settings once). ## Layout ``` +Examples/ + HelloPlaydate/ Minimal game buildable into a simulator .pdx Scripts/ install-pkgconfig.sh One-time setup: points the "playdate" pkg-config module at your SDK installation @@ -362,12 +340,12 @@ Sources/ importing pd_api.h from the SDK, plus inline shims for the variadic log/error functions PlayDate/ The Swift bindings, one file per subsystem - (Sound and Graphics are split across several files) + (Sound and Graphics are split across several files), + plus the PlayDate.docc documentation catalog Tests/ PlayDate/ Host-runnable tests for the pure value types ``` ## License -MIT — see [LICENSE](LICENSE). The Playdate SDK itself is licensed separately -by Panic, Inc. and is not distributed with this package. +MIT — see [LICENSE](LICENSE). The Playdate SDK itself is licensed separately by Panic, Inc. and is not distributed with this package. diff --git a/Sources/PlayDate/PlayDate.docc/GettingStarted.md b/Sources/PlayDate/PlayDate.docc/GettingStarted.md new file mode 100644 index 0000000..eed9be8 --- /dev/null +++ b/Sources/PlayDate/PlayDate.docc/GettingStarted.md @@ -0,0 +1,66 @@ +# Getting Started + +Bootstrap the bindings from your game's entry point and drive a frame loop. + +## Overview + +A Playdate game has a single C entry point, `eventHandler`, which the +firmware calls with a `PlaydateAPI*` and an event code. Export it with +`@_cdecl`, call ``Playdate/initialize(with:)`` on the first event, and +install an update callback: + +```swift +import CPlaydate +import PlayDate + +@_cdecl("eventHandler") +func eventHandler( + pointer: UnsafeMutableRawPointer, + event: PDSystemEvent, + argument: UInt32 +) -> Int32 { + if case .initialize = SystemEvent(event: event, argument: argument) { + Playdate.initialize(with: pointer) // must happen before anything else + Game.shared.start() + } + + return 0 +} + +final class Game { + nonisolated(unsafe) static let shared = Game() + + func start() { + Display.setRefreshRate(50) + + System.setUpdateCallback { + self.update() + return true // true = redraw the display this frame + } + } + + func update() { + let (_, pushed, _) = System.buttonState + if pushed.contains(.a) { + System.log("A pressed") + } + + Graphics.clear(color: .white) + Graphics.drawText("Hello, Playdate", x: 8, y: 8) + System.drawFPS() + } +} +``` + +## Conventions to know + +- **Initialization.** Calling any wrapper before + ``Playdate/initialize(with:)`` is a programmer error and will crash. +- **Errors.** Fallible operations use typed throws — ``PlaydateError`` + generally, ``Network/NetError`` for network I/O. +- **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 + OS-owned objects don't free them — keep the owner alive instead, as + documented on each API. +- **Threading.** The Playdate runtime is single-threaded; don't call the + API from other threads. diff --git a/Sources/PlayDate/PlayDate.docc/PlayDate.md b/Sources/PlayDate/PlayDate.docc/PlayDate.md new file mode 100644 index 0000000..2ea7a76 --- /dev/null +++ b/Sources/PlayDate/PlayDate.docc/PlayDate.md @@ -0,0 +1,61 @@ +# ``PlayDate`` + +Swift bindings to the Playdate C API. + +## Overview + +The Playdate C API is delivered as a `PlaydateAPI*` struct of function +pointers that the firmware hands to your game at launch. This module wraps +that surface in idiomatic Swift: top-level namespaces per subsystem, wrapper +types with ownership semantics, closures instead of function-pointer/userdata +pairs, `OptionSet`s and `enum`s instead of raw constants, and typed `throws` +for fallible calls. + +Call ``Playdate/initialize(with:)`` from your game's `eventHandler` before +using anything else — see . + +The bindings are written within the Embedded Swift subset, so the same code +compiles for the Playdate Simulator and for the device +(`armv7em-none-none-eabi`). + +## Topics + +### Essentials + +- +- ``Playdate`` +- ``SystemEvent`` +- ``PlaydateError`` + +### System and display + +- ``System`` +- ``Display`` + +### Drawing + +- ``Graphics`` +- ``Rect`` + +### Sprites + +- ``Sprite`` + +### Audio + +- ``Sound`` + +### Storage + +- ``File`` +- ``JSON`` + +### Connectivity + +- ``Network`` +- ``Scoreboards`` +- ``AccessReply`` + +### Lua interop + +- ``Lua``