Low-level bindings to Playdate API with optional official documentation and optional lang-items.
That looks pretty hardcore. That's why there is four+ possible ways to get access to API endpoints, among them returning Option
or Result
.
I've experimented enough with wrapping the entire API with results at every step, then painstakingly optimized their unfolding. And in the end I came to the conclusion that in this exact project the number of errors/results should be minimized.
println
macroPLAYDATE_SDK_PATH
points to the SDK rootarm-none-eabi-gcc
or gcc-arm-none-eabi
in your PATH
Minimal example of the application
Cargo.toml: ```toml [lib] name = "example" path = "src/lib.rs" crate-type = [ "dylib", # for simulator "staticlib", # for hardware ]
[dependencies.pd] package = "playdate-sys" git = "this/repo/path.git" ```
src/lib.rs: ```rust
use core::ffi::*;
extern crate alloc;
extern crate pd; use pd::ffi::*;
// Note: _arg
is a key-code in simulator, otherwise it's just zero.
pub extern "C" fn eventHandlerShim(api: const PlaydateAPI, event: PDSystemEvent, _arg: u32) -> c_int {
match event {
PDSystemEvent::kEventInit => unsafe {
// register the API entry point
pd::API = api;
// get setUpdateCallback
fn
let f = ((*api).system).setUpdateCallback.unwrap();
// register update callback
f(Some(onupdate), core::ptr::nullmut());
// `println` uses `API` internally, that set above
println!("Init, Hello world!");
},
PDSystemEvent::kEventLock => println!("Lock"),
PDSystemEvent::kEventUnlock => println!("Unlock"),
PDSystemEvent::kEventPause => println!("Pause"),
PDSystemEvent::kEventResume => println!("Resume"),
PDSystemEvent::kEventLowPower => println!("LowPower"),
PDSystemEvent::kEventTerminate => println!("Terminate"),
PDSystemEvent::kEventInitLua => println!("InitLua"),
// simulator only, keyboard events:
PDSystemEvent::kEventKeyPressed => println!("KeyPressed"),
PDSystemEvent::kEventKeyReleased => println!("KeyReleased"),
}
0 // zero means "OK, no error, continue please"
}
unsafe extern "C" fn onupdate(: mut c_void) -> i32 { 1 / 1
means "OK, continue updates" */ }
```
.cargo/config.toml: ```toml [target.thumbv7em-none-eabihf] rustflags = [ "-Ctarget-cpu=cortex-m7", "-Clink-args=--emit-relocs", "-Crelocation-model=pic", "-Csoft-float=no", "-Clink-arg=--cref", "-Clink-arg=--gc-sections", "-Clink-arg=--entry=eventHandlerShim" ]
[unstable] unstable-options = true ```
arm-none-eabi-gcc ./target/thumbv7em-none-eabihf/release/libexample.a \ -nostartfiles -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16 -D_FPUUSED=1 \ -Wl,--cref,--gc-sections,--no-warn-mismatch,--emit-relocs -mword-relocations \ -fno-common -fno-exceptions \ -T$PLAYDATESDKPATH/CAPI/buildsupport/linkmap.ld \ -o ./target/thumbv7em-none-eabihf/release/example.elf \ --entry eventHandlerShim
$PLAYDATE_SDK_PATH/bin/pdc
with path of prepared package.```
$PLAYDATE_SDK_PATH/bin/pdc
with path of prepared package.⚠️ Note that cargo-playdate can do it all for you easily. Also it can build executable binaries.
See examples (🚨 TODO: LINK HERE).
Little recommendation is to add this to your Cargo.toml ```toml [profile.dev] panic = "abort"
[profile.release]
panic = "abort"
opt-level = "s" # optimize for binary size (or use 3
, play with it)
overflow-checks = false # runtime integer overflow checks. (optionally, as you wish)
lto = "fat"
incremental = false
codegen-units = 1
debug = 0 strip = "symbols" # or debuginfo debug-assertions = false ```
This is just recommendation because this is entirely optional including panic = "abort"
for example (unwinding there is challenging but not impossible).
There is some main kinds of features that you can control:
- bindgen-...
controls binding generator and extra codegen features
- bindings-...
to enable derive
s for types and some codegen like documentation
- lang-items and other hand-crafted things.
This crate contains some minimal required "parts" to build your application.
You can disable the features to prevent them from being and so you'll can use other allocator or panic-handler for example.
allocator
: global allocatorpanic-handler
: global panic handlereh-personality
: eh_personality for simulator-targets, dummy, empty, no-opNon-default features:
- entry-point
: simple minimal proxy entry point that caching API endpoint when app init.
By default if we have pregenerated bindings for your configuration (target, profile, derives, etc...) we use it instead of build new.
⚠️ Env var PLAYDATE_SDK_PATH
have to points to the PlayDate SDK directory as described in official documentation.
To prevent this behavior to use only pre-built bindings set env var IGNORE_EXISTING_PLAYDATE_SDK=1
.
There's bindgen
used to generate bindings, and some features are re-exported:
bindgen-runtime
to on/off bindgen uses runtime linking (dlopen) with libclang. More in bindgen docs.use features like bindings-derive-{name}
to ask bindgen to derive {name}
to all entities if it possible
full list of derive- features
bindings-derive-default
This crate should remain as low-level as possible.
It is possible to add extra things there only if that is: - really small things - absolutely necessary - implementations of third-party traits (from other crates including core) for api types - feature-gated, in case the thing has dependencies or isn't so small.
You can add functionality that based on this package. Just create a new your oun package on-top this and re-export all features.
I must repeat myself, I apologize for intruding.
⚠️ If you want to create a library that based on this library, please share all features of this package to allow everyone to properly configure entire dependency tree.
This makes kind of paramount importance if a user is using several of these extensions and it can be a mess if they dictate different configurations for this package.
cargo new name-of-your-extension
cargo add playdate-sys
toml
[features]
default = ["playdate-sys/default"]
lang-items = ["playdate-sys/lang-items"]
allocator = ["playdate-sys/allocator"]
bindings-derive-debug = ["playdate-sys/bindings-derive-debug"]
bindings-derive-default = ["playdate-sys/bindings-derive-default"]
...
This is full example how to generate bindings with doc-comments,
but documentation is optional, so you can omit it - just remove all about docs:
- bindings-documentation
feature
- execution playdate-docs-parser
- execution rustfmt
```bash mk dir -p ./api/sys/gen
export PDBUILDPREBUILT=1
DERIVESALL=bindings-derive-default,bindings-derive-eq,bindings-derive-copy,bindings-derive-debug,bindings-derive-hash,bindings-derive-ord,bindings-derive-partialeq,bindings-derive-partialord DERIVESDEF=bindings-derive-debug
FEATURESDEF=--features=bindings-documentation,$DERIVESDEF FEATURESALL=--features=bindings-documentation,$DERIVESALL
cargo build -p=playdate-sys $FEATURESDEF -vv cargo build -p=playdate-sys $FEATURESDEF --release cargo build -p=playdate-sys $FEATURESDEF --target=thumbv7em-none-eabihf cargo build -p=playdate-sys $FEATURESDEF --target=thumbv7em-none-eabihf --release
cargo build -p=playdate-sys $FEATURESALL -vv cargo build -p=playdate-sys $FEATURESALL --release cargo build -p=playdate-sys $FEATURESALL --target=thumbv7em-none-eabihf cargo build -p=playdate-sys $FEATURESALL --target=thumbv7em-none-eabihf --release
rustfmt ./api/sys/gen/*.rs ```
Important note:
To trigger changing prebuilt bindings, you need set env var: PD_BUILD_PREBUILT
, that allows changes outside of OUT_DIR
.