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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||||
+4
-1
@@ -1,6 +1,9 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/.build
|
.build/
|
||||||
/Packages
|
/Packages
|
||||||
|
# Example build products
|
||||||
|
Examples/*/Source/pdex.dylib
|
||||||
|
*.pdx
|
||||||
xcuserdata/
|
xcuserdata/
|
||||||
DerivedData/
|
DerivedData/
|
||||||
.swiftpm/configuration/registries.json
|
.swiftpm/configuration/registries.json
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+21
@@ -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"
|
||||||
@@ -10,6 +10,10 @@ let package = Package(
|
|||||||
targets: ["PlayDate"]
|
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: [
|
targets: [
|
||||||
// The Playdate C API headers, resolved through the "playdate"
|
// The Playdate C API headers, resolved through the "playdate"
|
||||||
// pkg-config module. Run Scripts/install-pkgconfig.sh once to point
|
// pkg-config module. Run Scripts/install-pkgconfig.sh once to point
|
||||||
|
|||||||
@@ -4,12 +4,7 @@
|
|||||||
|
|
||||||
Swift bindings to the [Playdate](https://play.date) C API.
|
Swift bindings to the [Playdate](https://play.date) C API.
|
||||||
|
|
||||||
The Playdate C API is delivered as a `PlaydateAPI*` struct of function
|
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.
|
||||||
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:
|
All ten C subsystems are covered:
|
||||||
|
|
||||||
@@ -28,28 +23,20 @@ All ten C subsystems are covered:
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- The [Playdate SDK](https://play.date/dev/) (3.1.1 or later). The SDK is
|
- The [Playdate SDK](https://play.date/dev/) (3.1.1 or later). The SDK is not vendored into this repository.
|
||||||
not vendored into this repository.
|
|
||||||
- Swift 6.4 tools or later.
|
- Swift 6.4 tools or later.
|
||||||
|
|
||||||
### One-time SDK setup
|
### One-time SDK setup
|
||||||
|
|
||||||
The `CPlaydate` target resolves `pd_api.h` through a `playdate` pkg-config
|
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:
|
||||||
module, so the package works as a normal SwiftPM dependency without unsafe
|
|
||||||
build flags. Generate the pkg-config file once per machine:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
Scripts/install-pkgconfig.sh
|
Scripts/install-pkgconfig.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The script locates the SDK via `PLAYDATE_SDK_PATH` (default
|
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`.
|
||||||
`~/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
|
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.
|
||||||
manifest (File ▸ Packages ▸ Reset Package Caches) — Xcode caches package
|
|
||||||
resolution and won't notice the new `.pc` file on its own.
|
|
||||||
|
|
||||||
## Adding the dependency
|
## Adding the dependency
|
||||||
|
|
||||||
@@ -70,18 +57,18 @@ targets: [
|
|||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
A Playdate game has a single C entry point, `eventHandler`. Export it with
|
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:
|
||||||
`@_cdecl`, initialize the binding on the first event, and install an update
|
|
||||||
callback:
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
import CPlaydate
|
import CPlaydate
|
||||||
import PlayDate
|
import PlayDate
|
||||||
|
|
||||||
@_cdecl("eventHandler")
|
@_cdecl("eventHandler")
|
||||||
func eventHandler(pointer: UnsafeMutableRawPointer,
|
func eventHandler(
|
||||||
|
pointer: UnsafeMutableRawPointer,
|
||||||
event: PDSystemEvent,
|
event: PDSystemEvent,
|
||||||
argument: UInt32) -> Int32 {
|
argument: UInt32
|
||||||
|
) -> Int32 {
|
||||||
switch SystemEvent(event: event, argument: argument) {
|
switch SystemEvent(event: event, argument: argument) {
|
||||||
case .initialize:
|
case .initialize:
|
||||||
Playdate.initialize(with: pointer) // must happen before anything else
|
Playdate.initialize(with: pointer) // must happen before anything else
|
||||||
@@ -91,6 +78,7 @@ func eventHandler(pointer: UnsafeMutableRawPointer,
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +108,7 @@ final class Game {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Playdate.initialize(with:)` stores the API pointer once; every wrapper in
|
`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.
|
||||||
the module uses it from then on. Calling any wrapper before `initialize` is
|
|
||||||
a programmer error and will crash.
|
|
||||||
|
|
||||||
## Tour of the API
|
## Tour of the API
|
||||||
|
|
||||||
@@ -202,10 +188,7 @@ for collision in collisions where collision.other.tag == Tags.brick {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Sprite callbacks (`setUpdateFunction`, `setDrawFunction`,
|
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.
|
||||||
`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
|
### Sound
|
||||||
|
|
||||||
@@ -295,61 +278,56 @@ try Lua.addFunction(double, name: "mylib.double")
|
|||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- **Namespaces.** The subsystem namespaces (`System`, `Graphics`, `Sound`,
|
- **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`.
|
||||||
…) live at the top level of the module; only the raw C API bootstrap
|
- **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.
|
||||||
stays under `Playdate` (`Playdate.initialize(with:)`, `Playdate.api`).
|
- **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.
|
||||||
On a name collision with another module, qualify with the module name:
|
- **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
|
||||||
`PlayDate.System`.
|
completions, `getServerTime`); those track one Swift closure at a time, as noted in their documentation.
|
||||||
- **Errors.** Fallible operations use typed throws — `throws(PlaydateError)`
|
- **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.
|
||||||
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
|
## Building for the simulator and device
|
||||||
|
|
||||||
This package builds as a plain Swift library, which is how you develop and
|
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).
|
||||||
unit-test game logic on the host (`swift test` works out of the box).
|
|
||||||
|
|
||||||
Shipping a `.pdx` needs the Playdate toolchain on top:
|
Shipping a `.pdx` needs the Playdate toolchain on top:
|
||||||
|
|
||||||
- **Simulator** builds compile your game as a host dylib placed in the pdx.
|
- **Simulator** builds compile your game as a host dylib placed in the pdx.
|
||||||
- **Device** builds require Embedded Swift for ARM Cortex-M7
|
- **Device** builds require Embedded Swift for ARM Cortex-M7 (`-enable-experimental-feature Embedded`, triple `armv7em-none-none-eabi`).
|
||||||
(`-enable-experimental-feature Embedded`, triple `armv7em-none-none-eabi`).
|
|
||||||
|
|
||||||
The wrappers are written within the Embedded Swift subset for exactly this
|
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:
|
||||||
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
|
```sh
|
||||||
SWIFT_BIN=~/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-<date>.xctoolchain/usr/bin/swift \
|
SWIFT_BIN=~/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-<date>.xctoolchain/usr/bin/swift \
|
||||||
Scripts/build-embedded.sh
|
Scripts/build-embedded.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
See Apple's
|
See Apple's [swift-playdate-examples](https://github.com/apple/swift-playdate-examples) for a working Makefile/toolchain setup that this library slots into.
|
||||||
[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
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Examples/
|
||||||
|
HelloPlaydate/ Minimal game buildable into a simulator .pdx
|
||||||
Scripts/
|
Scripts/
|
||||||
install-pkgconfig.sh One-time setup: points the "playdate" pkg-config
|
install-pkgconfig.sh One-time setup: points the "playdate" pkg-config
|
||||||
module at your SDK installation
|
module at your SDK installation
|
||||||
@@ -362,12 +340,12 @@ Sources/
|
|||||||
importing pd_api.h from the SDK, plus inline shims for
|
importing pd_api.h from the SDK, plus inline shims for
|
||||||
the variadic log/error functions
|
the variadic log/error functions
|
||||||
PlayDate/ The Swift bindings, one file per subsystem
|
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/
|
Tests/
|
||||||
PlayDate/ Host-runnable tests for the pure value types
|
PlayDate/ Host-runnable tests for the pure value types
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT — see [LICENSE](LICENSE). The Playdate SDK itself is licensed separately
|
MIT — see [LICENSE](LICENSE). The Playdate SDK itself is licensed separately by Panic, Inc. and is not distributed with this package.
|
||||||
by Panic, Inc. and is not distributed with this package.
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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 <doc:GettingStarted>.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- <doc:GettingStarted>
|
||||||
|
- ``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``
|
||||||
Reference in New Issue
Block a user