From 4b5349956ce5032103f6e13a4c58459fec734c89 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 13:36:16 +0400 Subject: [PATCH 1/9] sprite api, improve bitmap, add trait to sys --- .github/workflows/tests.yml | 50 +- Cargo.lock | 36 +- api/gfx/Cargo.toml | 2 +- api/gfx/README.md | 6 +- api/gfx/src/bitmap/bitmap.rs | 102 +++- api/gfx/src/lib.rs | 30 ++ api/sprite/Cargo.toml | 107 +++++ api/sprite/README.md | 32 ++ api/sprite/examples/README.md | 19 + api/sprite/examples/handler.rs | 216 +++++++++ api/sprite/examples/simple.rs | 150 ++++++ api/sprite/src/api.rs | 511 ++++++++++++++++++++ api/sprite/src/callback/collision.rs | 298 ++++++++++++ api/sprite/src/callback/draw.rs | 307 ++++++++++++ api/sprite/src/callback/func.rs | 96 ++++ api/sprite/src/callback/update.rs | 289 +++++++++++ api/sprite/src/ext.rs | 45 ++ api/sprite/src/lib.rs | 273 +++++++++++ api/sprite/src/sprite.rs | 691 +++++++++++++++++++++++++++ api/sys/Cargo.toml | 2 +- api/sys/src/lib.rs | 2 + api/sys/src/traits.rs | 21 + cargo/Cargo.toml | 2 +- 23 files changed, 3239 insertions(+), 48 deletions(-) create mode 100644 api/sprite/Cargo.toml create mode 100644 api/sprite/README.md create mode 100644 api/sprite/examples/README.md create mode 100644 api/sprite/examples/handler.rs create mode 100644 api/sprite/examples/simple.rs create mode 100644 api/sprite/src/api.rs create mode 100644 api/sprite/src/callback/collision.rs create mode 100644 api/sprite/src/callback/draw.rs create mode 100644 api/sprite/src/callback/func.rs create mode 100644 api/sprite/src/callback/update.rs create mode 100644 api/sprite/src/ext.rs create mode 100644 api/sprite/src/lib.rs create mode 100644 api/sprite/src/sprite.rs create mode 100644 api/sys/src/traits.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 57832932..f38051be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,7 +2,7 @@ name: Tests on: pull_request: push: - branches: [main, master] + branches: [main, master, "**"] env: CARGO_INCREMENTAL: 1 @@ -13,6 +13,18 @@ env: # logging: RUST_LOG: trace CARGO_PLAYDATE_LOG: trace + # crates lists: + API_CRATES: >- + -p=playdate-sys + -p=playdate-fs + -p=playdate-sound + -p=playdate-color + -p=playdate-controls + -p=playdate-menu + -p=playdate-graphics + -p=playdate-display + -p=playdate-system + -p=playdate-sprite jobs: api: @@ -85,34 +97,19 @@ jobs: cargo test -p=playdate-sys --features=$FEATURES_1 -- --nocapture cargo test -p=playdate-sys --features=$FEATURES_2 -- --nocapture - cargo test -p=playdate-fs --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-fs --lib --no-default-features --features=$FEATURES_2 -- --nocapture + cargo test ${{ env.API_CRATES }} --lib --no-default-features --features=$FEATURES_1 -- --nocapture + cargo test ${{ env.API_CRATES }} --lib --no-default-features --features=$FEATURES_2 -- --nocapture - cargo test -p=playdate-sound --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-sound --lib --no-default-features --features=$FEATURES_2 -- --nocapture - - cargo test -p=playdate-color --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-color --lib --no-default-features --features=$FEATURES_2 -- --nocapture - - cargo test -p=playdate-controls --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-controls --lib --no-default-features --features=$FEATURES_2 -- --nocapture - - cargo test -p=playdate-menu --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-menu --lib --no-default-features --features=$FEATURES_2 -- --nocapture - - cargo test -p=playdate-graphics --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-graphics --lib --no-default-features --features=$FEATURES_2 -- --nocapture - - cargo test -p=playdate-display --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-display --lib --no-default-features --features=$FEATURES_2 -- --nocapture - - cargo test -p=playdate-system --lib --no-default-features --features=$FEATURES_1 -- --nocapture - cargo test -p=playdate-system --lib --no-default-features --features=$FEATURES_2 -- --nocapture + - name: Check device target + run: | + # should we check with -Zbuild-std ? + cargo check ${{ env.API_CRATES }} --target=thumbv7em-none-eabihf - name: Examples run: | FEATURES=bindgen-runtime,bindings-derive-debug + # TODO: use ${{ env.API_CRATES }} when all crates will have example cargo build --target=thumbv7em-none-eabihf -p=playdate-fs --examples --features=$FEATURES -Zbuild-std cargo build --target=thumbv7em-none-eabihf -p=playdate-controls --examples --features=$FEATURES -Zbuild-std cargo build --target=thumbv7em-none-eabihf -p=playdate-color --examples --features=$FEATURES -Zbuild-std @@ -245,7 +242,10 @@ jobs: with: path: | ~/.cargo - key: ${{ runner.os }}-cargo-tests-${{ hashFiles('Cargo.lock') }} + key: ${{ runner.os }}-cargo-tests-examples-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-tests-examples-${{ hashFiles('Cargo.lock') }} + ${{ runner.os }}-cargo-tests-${{ hashFiles('Cargo.lock') }} - name: Config run: | @@ -253,6 +253,7 @@ jobs: rm -rf ./target/playdate || true mkdir -p .cargo cp -rf .github/config.toml .cargo/config.toml + cargo clean - name: Cache LLVM id: cache-llvm @@ -290,6 +291,7 @@ jobs: run: | FEATURES=bindgen-runtime,bindings-derive-debug + # TODO: use ${{ env.API_CRATES }} when all crates will have example cargo playdate package --simulator --device -p=playdate-sys --examples --features=$FEATURES cargo playdate package --simulator --device -p=playdate-fs --examples --features=$FEATURES cargo playdate package --simulator --device -p=playdate-controls --examples --features=$FEATURES diff --git a/Cargo.lock b/Cargo.lock index fe49a475..58bb25bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,7 +164,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.36", + "syn 2.0.37", "which", ] @@ -337,7 +337,7 @@ dependencies = [ "sha1", "shell-escape", "strip-ansi-escapes", - "syn 2.0.36", + "syn 2.0.37", "tar", "tempfile", "termcolor", @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "cargo-playdate" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "byteorder", @@ -501,7 +501,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -2556,7 +2556,7 @@ dependencies = [ "proc-macro2", "quote", "semver", - "syn 2.0.36", + "syn 2.0.37", "which", ] @@ -2615,7 +2615,7 @@ dependencies = [ [[package]] name = "playdate-graphics" -version = "0.1.3" +version = "0.1.4" dependencies = [ "playdate-color", "playdate-fs", @@ -2637,9 +2637,17 @@ dependencies = [ "playdate-sys", ] +[[package]] +name = "playdate-sprite" +version = "0.1.0" +dependencies = [ + "playdate-graphics", + "playdate-sys", +] + [[package]] name = "playdate-sys" -version = "0.1.6" +version = "0.1.7" dependencies = [ "heapless", "playdate-bindgen", @@ -2712,7 +2720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -3034,7 +3042,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -3264,9 +3272,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.36" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -3349,7 +3357,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", ] [[package]] @@ -3607,7 +3615,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", "wasm-bindgen-shared", ] @@ -3629,7 +3637,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.36", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/api/gfx/Cargo.toml b/api/gfx/Cargo.toml index 16f1d956..44d0a9e8 100644 --- a/api/gfx/Cargo.toml +++ b/api/gfx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-graphics" -version = "0.1.3" +version = "0.1.4" edition = "2021" readme = "README.md" diff --git a/api/gfx/README.md b/api/gfx/README.md index 761e026a..71b3375b 100644 --- a/api/gfx/README.md +++ b/api/gfx/README.md @@ -7,18 +7,20 @@ High-level graphics API built on-top of [playdate-sys][]. ```rust extern crate playdate_graphics; -use playdate_graphics::{bitmap, color, text}; +use playdate_graphics::{bitmap, color, text, BitmapFlipExt}; // create and draw black rect: let image = bitmap::Bitmap::new(100, 100, color::Color::BLACK).unwrap(); -image.draw(50, 100, bitmap::BitmapFlip::kBitmapUnflipped); +image.draw(50, 100, bitmap::BitmapFlip::Unflipped); // draw simple line of text: let str = CStr::from_bytes_with_nul(b"Simple Text\0").unwrap(); text::draw_text_cstr(str, 40, 40); ``` +More covered in [examples][gh-examples]. +[gh-examples]: https://github.com/boozook/playdate/tree/main/api/gfx/examples [playdate-sys]: https://crates.io/crates/playdate-sys diff --git a/api/gfx/src/bitmap/bitmap.rs b/api/gfx/src/bitmap/bitmap.rs index 251d9618..bea4a8df 100644 --- a/api/gfx/src/bitmap/bitmap.rs +++ b/api/gfx/src/bitmap/bitmap.rs @@ -1,13 +1,16 @@ use core::ffi::c_char; use core::ffi::c_float; use core::ffi::c_int; +use core::marker::PhantomData; use alloc::boxed::Box; +use sys::traits::AsRaw; use sys::ffi::CString; use sys::ffi::LCDColor; use sys::ffi::LCDRect; use sys::ffi::LCDBitmap; pub use sys::ffi::LCDBitmapFlip as BitmapFlip; +pub use sys::ffi::LCDBitmapDrawMode as BitmapDrawMode; use fs::Path; pub use color::*; use crate::error::ApiError; @@ -15,10 +18,103 @@ use crate::error::Error; use super::api; +pub trait AnyBitmap: AsRaw + BitmapApi {} + +impl AnyBitmap for &'_ T {} + +impl AnyBitmap for BitmapRef<'_> {} + +impl AnyBitmap for Bitmap {} + +pub trait BitmapApi { + type Api: api::Api; + fn api(&self) -> Self::Api + where Self::Api: Copy; + fn api_ref(&self) -> &Self::Api; +} + +impl BitmapApi for BitmapRef<'_> { + type Api = api::Default; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + api::Default::default() + } + + fn api_ref(&self) -> &Self::Api { &self.1 } +} + +impl BitmapApi for Bitmap { + type Api = Api; + fn api(&self) -> Api + where Self::Api: Copy { + self.1 + } + + fn api_ref(&self) -> &Self::Api { &self.1 } +} + +impl BitmapApi for &'_ T { + type Api = T::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + (*self).api() + } + + fn api_ref(&self) -> &Self::Api { (*self).api_ref() } +} + + #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))] pub struct Bitmap(pub(crate) *mut LCDBitmap, pub(crate) Api); +impl AsRaw for Bitmap { + type Type = LCDBitmap; + unsafe fn as_raw(&self) -> *mut LCDBitmap { self.0 } +} + +impl From<*mut LCDBitmap> for Bitmap { + fn from(ptr: *mut LCDBitmap) -> Self { Self(ptr, Api::default()) } +} + +impl Bitmap { + /// Convert this bitmap into the same bitmap that will not be freed on drop. + /// That means that only C-part of the bitmap will be freed. + /// + /// __Safety is guaranteed by the caller.__ + pub fn into_shared(mut self) -> Bitmap { + let res = Bitmap(self.0, self.1); + self.0 = core::ptr::null_mut(); + res + } +} + + +#[repr(transparent)] +pub struct BitmapRef<'owner>(*mut LCDBitmap, api::Default, PhantomData<&'owner ()>); + +impl AsRaw for BitmapRef<'_> { + type Type = LCDBitmap; + unsafe fn as_raw(&self) -> *mut LCDBitmap { self.0 } +} + +impl From<*mut LCDBitmap> for BitmapRef<'_> { + fn from(ptr: *mut LCDBitmap) -> Self { Self(ptr, Default::default(), PhantomData) } +} + +impl<'owner> BitmapRef<'owner> { + pub fn into_bitmap(self) -> Bitmap<::Api, false> { + Bitmap(unsafe { self.as_raw() }, self.api()) + } + + pub fn into_bitmap_with(self, api: Api) -> Bitmap { + Bitmap(unsafe { self.as_raw() }, api) + } +} + + impl Bitmap { pub fn new(width: c_int, height: c_int, bg: Color) -> Result where Api: Default { @@ -105,9 +201,6 @@ impl Clone for Bitmap { } } } -// impl Clone for Bitmap { -// fn clone(&self) -> Self { Self(self.0, self.1.clone()) } -// } impl Bitmap { @@ -178,7 +271,6 @@ impl Bitmap { /// Gets a mask image for the given bitmap. If the image doesn’t have a mask, returns None. /// /// Clones inner api-access. - // XXX: investigate is it should be free-on-drop? #[inline(always)] pub fn get_mask(&self) -> Option> where Api: Clone { @@ -312,7 +404,7 @@ pub struct BitmapData<'bitmap> { impl<'bitmap> BitmapData<'bitmap> { pub fn width(&self) -> c_int { self.width } pub fn height(&self) -> c_int { self.height } - pub fn rowbytes(&self) -> c_int { self.row_bytes } + pub fn row_bytes(&self) -> c_int { self.row_bytes } pub fn mask(&self) -> Option<&[u8]> { self.mask.as_deref() } pub fn mask_mut(&mut self) -> Option<&mut [u8]> { self.mask.as_deref_mut() } pub fn data(&self) -> &[u8] { self.data } diff --git a/api/gfx/src/lib.rs b/api/gfx/src/lib.rs index 57e06e63..4e4d8511 100644 --- a/api/gfx/src/lib.rs +++ b/api/gfx/src/lib.rs @@ -14,6 +14,9 @@ pub mod bitmap { pub use bitmap::*; } +pub use sys::ffi::LCDBitmapFlip as BitmapFlip; +pub use sys::ffi::LCDBitmapDrawMode as BitmapDrawMode; + pub use bitmap::get_debug_bitmap; pub use bitmap::get_display_buffer_bitmap; pub use bitmap::copy_frame_buffer_bitmap; @@ -219,3 +222,30 @@ pub fn fill_ellipse(x: c_int, let f = *sys::api!(graphics.fillEllipse); unsafe { f(x, y, width, height, start_angle, end_angle, color) } } + + +pub trait BitmapFlipExt { + #![allow(non_upper_case_globals)] + const Unflipped: BitmapFlip = BitmapFlip::kBitmapUnflipped; + const FlippedX: BitmapFlip = BitmapFlip::kBitmapFlippedX; + const FlippedY: BitmapFlip = BitmapFlip::kBitmapFlippedY; + const FlippedXY: BitmapFlip = BitmapFlip::kBitmapFlippedXY; +} + +impl BitmapFlipExt for BitmapFlip {} + + +pub trait BitmapDrawModeExt { + #![allow(non_upper_case_globals)] + const Copy: BitmapDrawMode = BitmapDrawMode::kDrawModeCopy; + const WhiteTransparent: BitmapDrawMode = BitmapDrawMode::kDrawModeWhiteTransparent; + const BlackTransparent: BitmapDrawMode = BitmapDrawMode::kDrawModeBlackTransparent; + const FillWhite: BitmapDrawMode = BitmapDrawMode::kDrawModeFillWhite; + const FillBlack: BitmapDrawMode = BitmapDrawMode::kDrawModeFillBlack; + const XOR: BitmapDrawMode = BitmapDrawMode::kDrawModeXOR; + const NXOR: BitmapDrawMode = BitmapDrawMode::kDrawModeNXOR; + const Inverted: BitmapDrawMode = BitmapDrawMode::kDrawModeInverted; +} + + +impl BitmapDrawModeExt for BitmapDrawMode {} diff --git a/api/sprite/Cargo.toml b/api/sprite/Cargo.toml new file mode 100644 index 00000000..4412a8e9 --- /dev/null +++ b/api/sprite/Cargo.toml @@ -0,0 +1,107 @@ +[package] +name = "playdate-sprite" +version = "0.1.0" +edition = "2021" + +readme = "README.md" +license = "MIT OR Apache-2.0" +authors = ["Alex Koz "] +description = "High-level sprite API built on-top of Playdate API" +keywords = ["playdate", "sdk", "api", "gamedev"] +categories = [ + "game-development", + "api-bindings", + "rendering::graphics-api", + "graphics", + "no-std", +] +homepage = "https://github.com/boozook/playdate" +repository = "https://github.com/boozook/playdate.git" + + +[features] +default = ["sys/default", "gfx/default"] +# sys- features: +lang-items = ["sys/lang-items", "gfx/lang-items"] +allocator = ["sys/allocator", "gfx/allocator"] +panic-handler = ["sys/panic-handler", "gfx/panic-handler"] +eh-personality = ["sys/eh-personality", "gfx/eh-personality"] +error-ctx = ["sys/error-ctx", "gfx/error-ctx"] +bindgen-runtime = ["sys/bindgen-runtime", "gfx/bindgen-runtime"] +bindgen-static = ["sys/bindgen-static", "gfx/bindgen-static"] +bindings-derive-default = [ + "sys/bindings-derive-default", + "gfx/bindings-derive-default", +] +bindings-derive-eq = ["sys/bindings-derive-eq", "gfx/bindings-derive-eq"] +bindings-derive-copy = ["sys/bindings-derive-copy", "gfx/bindings-derive-copy"] +bindings-derive-debug = [ + "sys/bindings-derive-debug", + "gfx/bindings-derive-debug", +] +bindings-derive-hash = ["sys/bindings-derive-hash", "gfx/bindings-derive-hash"] +bindings-derive-ord = ["sys/bindings-derive-ord", "gfx/bindings-derive-ord"] +bindings-derive-partialeq = [ + "sys/bindings-derive-partialeq", + "gfx/bindings-derive-partialeq", +] +bindings-derive-partialord = [ + "sys/bindings-derive-partialord", + "gfx/bindings-derive-partialord", +] +bindings-derive-constparamty = [ + "sys/bindings-derive-constparamty", + "gfx/bindings-derive-constparamty", +] +bindings-documentation = [ + "sys/bindings-documentation", + "gfx/bindings-documentation", +] + + +[dependencies.sys] +version = "0.1" +path = "../sys" +package = "playdate-sys" +default-features = false + +[dependencies.gfx] +version = "0.1" +path = "../gfx" +package = "playdate-graphics" +default-features = false + + +[[example]] +name = "simple" +crate-type = ["dylib", "staticlib"] +path = "examples/simple.rs" + +[[example]] +name = "handler" +crate-type = ["dylib", "staticlib"] +path = "examples/handler.rs" + +[package.metadata.playdate] +bundle-id = "rs.playdate.sprite" + + +[package.metadata.docs.rs] +all-features = false +features = [ + "bindings-derive-default", + "bindings-derive-eq", + "bindings-derive-copy", + "bindings-derive-debug", + "bindings-derive-hash", + "bindings-derive-ord", + "bindings-derive-partialeq", + "bindings-derive-partialord", +] +rustdoc-args = ["--cfg", "docsrs", "--show-type-layout"] +default-target = "thumbv7em-none-eabihf" +cargo-args = [ + "-Zunstable-options", + "-Zrustdoc-scrape-examples", + "-Zbuild-std=core,alloc", +] diff --git a/api/sprite/README.md b/api/sprite/README.md new file mode 100644 index 00000000..b78cad02 --- /dev/null +++ b/api/sprite/README.md @@ -0,0 +1,32 @@ +# Sprite API for PlayDate + +High-level sprite API built on-top of [playdate-sys][]. + + +## Usage + +```rust +use playdate_sprite::*; +use playdate_graphics::*; +use playdate_graphics::bitmap::Bitmap; + +let bitmap = Bitmap::new(50, 50, Color::BLACK)?; +let sprite = Sprite::new(); + +sprite.set_draw_mode(BitmapDrawMode::Copy); +sprite.set_image(&bitmap, BitmapFlip::Unflipped); +sprite.move_to(CENTER_X as _, CENTER_Y as _); +sprite.add(); +... +``` + +More covered in [examples][gh-examples]. + +[gh-examples]: https://github.com/boozook/playdate/tree/main/api/sprite/examples +[playdate-sys]: https://crates.io/crates/playdate-sys + + + +- - - + +This software is not sponsored or supported by Panic. diff --git a/api/sprite/examples/README.md b/api/sprite/examples/README.md new file mode 100644 index 00000000..b1b1e53b --- /dev/null +++ b/api/sprite/examples/README.md @@ -0,0 +1,19 @@ +# Examples + +⚠️ All of the examples here are very low-level, except for the parts that directly demonstrate the functionality of this package. + + +# How to run + +```bash +# Simulator: +cargo playdate run -p=playdate-sprite --example=simple --features=bindgen-runtime,bindings-derive-debug +# Device: +cargo playdate run -p=playdate-sprite --example=handler --features=bindgen-runtime,bindings-derive-debug --device +``` + +More information how to use [cargo-playdate][] in help: `cargo playdate --help`. + + + +[cargo-playdate]: https://crates.io/crates/cargo-playdate diff --git a/api/sprite/examples/handler.rs b/api/sprite/examples/handler.rs new file mode 100644 index 00000000..04a6b87c --- /dev/null +++ b/api/sprite/examples/handler.rs @@ -0,0 +1,216 @@ +#![no_std] +extern crate alloc; + +#[macro_use] +extern crate sys; +extern crate gfx; +extern crate playdate_sprite as sprite; + +use core::ffi::*; +use alloc::boxed::Box; + +use sys::ffi::*; +use gfx::*; +use gfx::color::*; +use gfx::bitmap::Bitmap; +use sprite::prelude::*; +use sprite::callback::update::*; + +use self::updater::*; + + +const CENTER_X: u32 = LCD_COLUMNS / 2; +const CENTER_Y: u32 = LCD_ROWS / 2; + + +/// App state +struct State { + rotation: c_float, + bitmap: Option, + sprite: Option, Upd>>, +} + +impl State { + const fn new() -> Self { + Self { rotation: 0., + bitmap: None, + sprite: None } + } + + + /// Updates the state + fn update(&mut self) -> Option<()> { + sprite::update_and_draw_sprites(); + + // reuse bitmap: + if let Some(bitmap) = self.bitmap.as_ref() { + self.rotation += 1.0; + if self.rotation > 360.0 { + self.rotation = 0.0; + } + + bitmap.draw_rotated(CENTER_X as _, CENTER_Y as _, self.rotation, 0.5, 0.5, 1.0, 1.0); + } + + Some(()) + } + + + /// Event handler + fn event(&'static mut self, event: PDSystemEvent) -> Option<()> { + match event { + // initial setup + PDSystemEvent::kEventInit => { + unsafe { (*(*sys::API).display).setRefreshRate?(60.0) }; + + let bitmap = Bitmap::new(50, 50, Color::BLACK).expect("bitmap"); + let sprite = Sprite::new().into_update_handler::(); + + sprite.set_draw_mode(BitmapDrawMode::Copy); + sprite.set_image(&bitmap, BitmapFlip::Unflipped); + sprite.move_to(CENTER_X as _, CENTER_Y as _); + sprite.add(); + + sprite.set_userdata(UpdState::new()); + + self.sprite = Some(sprite); + self.bitmap = Some(bitmap); + }, + _ => {}, + } + Some(()) + } +} + + +mod updater { + use core::ffi::c_float; + + use super::sprite; + use sys::ffi::{LCD_COLUMNS, LCD_ROWS}; + use sys::traits::AsRaw; + use sprite::AnySprite; + use sprite::prelude::*; + use sprite::callback::update::{Handle, SpriteUpdate}; + + + pub struct Upd(Sprite); + + impl AsRef> for Upd { + fn as_ref(&self) -> &Sprite { &self.0 } + } + + impl From for Upd where Sprite: From { + fn from(ptr: T) -> Self { Self(Sprite::from(ptr)) } + } + + impl TypedSprite for Upd { + type Userdata = UD; + const FREE_ON_DROP: bool = false; + } + impl AsRaw for Upd { + type Type = ::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } + } + impl SpriteApi for Upd { + type Api = ::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { self.0.api_ref() } + } + + + impl SpriteUpdate for Upd + where T: AnySprite, + Self: From, + Self::Userdata: AsMut + { + fn on_update(sprite: &Handle, Self>) { + if let Some(UpdState { velocity }) = sprite.userdata().map(|ud| ud.as_mut()) { + let bounds = sprite.bounds(); + + if bounds.x < 0. || bounds.x + bounds.width > LCD_COLUMNS as _ { + velocity.x = -velocity.x; + } + + if bounds.y < 0. || bounds.y + bounds.height > LCD_ROWS as _ { + velocity.y = -velocity.y; + } + + sprite.move_by(velocity.x, velocity.y); + } + } + } + + + #[derive(Default)] + pub struct UpdState { + velocity: Point, + } + + impl UpdState { + pub fn new() -> Self { Self { velocity: Point::new(1., 2.) } } + } + + impl AsMut for UpdState { + fn as_mut(&mut self) -> &mut Self { self } + } + + #[derive(Default)] + struct Point { + pub x: T, + pub y: T, + } + + impl Point { + const fn new(x: T, y: T) -> Point { Point { x, y } } + } +} + + +#[no_mangle] +/// Proxy event handler, calls `State::event` +pub extern "C" fn eventHandlerShim(api: *const PlaydateAPI, event: PDSystemEvent, _arg: u32) -> c_int { + static mut STATE: Option> = None; + + match event { + PDSystemEvent::kEventInit => unsafe { + // register the API entry point + sys::API = api; + + // create game state + if STATE.is_none() { + STATE = Some(Box::new(State::new())); + } + let state = STATE.as_mut().unwrap().as_mut() as *mut State; + + // get `setUpdateCallback` fn + let f = (*(*api).system).setUpdateCallback.expect("setUpdateCallback"); + // register update callback with user-data = our state + f(Some(on_update), state.cast()); + }, + _ => {}, + } + + if let Some(state) = unsafe { STATE.as_mut() } { + state.event(event).and(Some(0)).unwrap_or(1) + } else { + 1 + } +} + + +/// Proxy update callback, calls `State::update` +unsafe extern "C" fn on_update(state: *mut c_void) -> i32 { + let ptr: *mut State = state.cast(); + let state = ptr.as_mut().expect("missed state"); + state.update().and(Some(1)).unwrap_or_default() +} + + +// Needed for debug build +ll_symbols!(); diff --git a/api/sprite/examples/simple.rs b/api/sprite/examples/simple.rs new file mode 100644 index 00000000..178a824e --- /dev/null +++ b/api/sprite/examples/simple.rs @@ -0,0 +1,150 @@ +#![no_std] +extern crate alloc; + +#[macro_use] +extern crate sys; +extern crate gfx; +extern crate playdate_sprite as sprite; + +use core::ffi::*; +use alloc::boxed::Box; + +use sys::ffi::*; +use gfx::color::*; +use gfx::bitmap::*; +use gfx::*; +use sprite::*; + + +const CENTER_X: u32 = LCD_COLUMNS / 2; +const CENTER_Y: u32 = LCD_ROWS / 2; + + +/// 2D point +struct Point { + x: T, + y: T, +} + +impl Point { + const fn new(x: T, y: T) -> Point { Point { x, y } } +} + + +/// App state +struct State { + rotation: c_float, + bitmap: Option, + sprite: Option, + velocity: Point, +} + +impl State { + const fn new() -> Self { + Self { rotation: 0., + bitmap: None, + sprite: None, + velocity: Point::new(1., 2.) } + } + + + /// Updates the state + fn update(&mut self) -> Option<()> { + sprite::update_and_draw_sprites(); + + // reuse bitmap: + if let Some(bitmap) = self.bitmap.as_ref() { + self.rotation += 1.0; + if self.rotation > 360.0 { + self.rotation = 0.0; + } + + bitmap.draw_rotated(CENTER_X as _, CENTER_Y as _, self.rotation, 0.5, 0.5, 1.0, 1.0); + } + + // move sprite + if let Some(sprite) = self.sprite.as_ref() { + let bounds = sprite.bounds(); + + if bounds.x < 0. || bounds.x + bounds.width > LCD_COLUMNS as _ { + self.velocity.x = -self.velocity.x; + } + + if bounds.y < 0. || bounds.y + bounds.height > LCD_ROWS as _ { + self.velocity.y = -self.velocity.y; + } + + sprite.move_by(self.velocity.x, self.velocity.y); + } + + Some(()) + } + + + /// Event handler + fn event(&'static mut self, event: PDSystemEvent) -> Option<()> { + match event { + // initial setup + PDSystemEvent::kEventInit => { + unsafe { (*(*sys::API).display).setRefreshRate?(60.0) }; + + let bitmap = Bitmap::new(50, 50, Color::BLACK).expect("bitmap"); + let sprite = Sprite::new(); + + sprite.set_draw_mode(BitmapDrawMode::Copy); + sprite.set_image(&bitmap, BitmapFlip::Unflipped); + sprite.move_to(CENTER_X as _, CENTER_Y as _); + sprite.add(); + + self.sprite = Some(sprite); + self.bitmap = Some(bitmap); + }, + _ => {}, + } + Some(()) + } +} + + +#[no_mangle] +/// Proxy event handler, calls `State::event` +pub extern "C" fn eventHandlerShim(api: *const PlaydateAPI, event: PDSystemEvent, _arg: u32) -> c_int { + static mut STATE: Option> = None; + + match event { + PDSystemEvent::kEventInit => unsafe { + // register the API entry point + sys::API = api; + + // create game state + if STATE.is_none() { + STATE = Some(Box::new(State::new())); + } + let state = STATE.as_mut().unwrap().as_mut() as *mut State; + + // get `setUpdateCallback` fn + let f = (*(*api).system).setUpdateCallback.expect("setUpdateCallback"); + // register update callback with user-data = our state + f(Some(on_update), state.cast()); + }, + _ => {}, + } + + if let Some(state) = unsafe { STATE.as_mut() } { + state.event(event).and(Some(0)).unwrap_or(1) + } else { + 1 + } +} + + +/// Proxy update callback, calls `State::update` +unsafe extern "C" fn on_update(state: *mut c_void) -> i32 { + let ptr: *mut State = state.cast(); + let state = ptr.as_mut().expect("missed state"); + state.update().and(Some(1)).unwrap_or_default() +} + + +// Needed for debug build +ll_symbols!(); diff --git a/api/sprite/src/api.rs b/api/sprite/src/api.rs new file mode 100644 index 00000000..f4203099 --- /dev/null +++ b/api/sprite/src/api.rs @@ -0,0 +1,511 @@ +use core::ffi::c_float; +use core::ffi::c_int; +use core::ffi::c_void; + +use sys::ffi::LCDBitmap; +use sys::ffi::LCDBitmapDrawMode; +use sys::ffi::LCDBitmapFlip; +use sys::ffi::LCDRect; +use sys::ffi::LCDSprite; +use sys::ffi::LCDSpriteCollisionFilterProc; +use sys::ffi::LCDSpriteDrawFunction; +use sys::ffi::LCDSpriteUpdateFunction; +use sys::ffi::PDRect; +use sys::ffi::SpriteCollisionInfo; +use sys::ffi::SpriteQueryInfo; + + +#[derive(Debug, Clone, Copy, core::default::Default)] +pub struct Default; + +impl Api for Default {} + + +pub trait Api { + /// Returns [`sys::ffi::playdate_sprite::setAlwaysRedraw`]. + #[doc(alias = "sys::ffi::playdate_sprite::setAlwaysRedraw")] + #[inline(always)] + fn set_always_redraw(&self) -> unsafe extern "C" fn(flag: c_int) { *sys::api!(sprite.setAlwaysRedraw) } + + + /// Returns [`sys::ffi::playdate_sprite::addDirtyRect`]. + #[doc(alias = "sys::ffi::playdate_sprite::addDirtyRect")] + #[inline(always)] + fn add_dirty_rect(&self) -> unsafe extern "C" fn(dirtyRect: LCDRect) { *sys::api!(sprite.addDirtyRect) } + + + /// Returns [`sys::ffi::playdate_sprite::drawSprites`]. + #[doc(alias = "sys::ffi::playdate_sprite::drawSprites")] + #[inline(always)] + fn draw_sprites(&self) -> unsafe extern "C" fn() { *sys::api!(sprite.drawSprites) } + + + /// Returns [`sys::ffi::playdate_sprite::updateAndDrawSprites`]. + #[doc(alias = "sys::ffi::playdate_sprite::updateAndDrawSprites")] + #[inline(always)] + fn update_and_draw_sprites(&self) -> unsafe extern "C" fn() { *sys::api!(sprite.updateAndDrawSprites) } + + + /// Returns [`sys::ffi::playdate_sprite::newSprite`]. + #[doc(alias = "sys::ffi::playdate_sprite::newSprite")] + #[inline(always)] + fn new_sprite(&self) -> unsafe extern "C" fn() -> *mut LCDSprite { *sys::api!(sprite.newSprite) } + + + /// Returns [`sys::ffi::playdate_sprite::freeSprite`]. + #[doc(alias = "sys::ffi::playdate_sprite::freeSprite")] + #[inline(always)] + fn free_sprite(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) { *sys::api!(sprite.freeSprite) } + + + /// Returns [`sys::ffi::playdate_sprite::copy`]. + #[doc(alias = "sys::ffi::playdate_sprite::copy")] + #[inline(always)] + fn copy(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> *mut LCDSprite { *sys::api!(sprite.copy) } + + + /// Returns [`sys::ffi::playdate_sprite::addSprite`]. + #[doc(alias = "sys::ffi::playdate_sprite::addSprite")] + #[inline(always)] + fn add_sprite(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) { *sys::api!(sprite.addSprite) } + + + /// Returns [`sys::ffi::playdate_sprite::removeSprite`]. + #[doc(alias = "sys::ffi::playdate_sprite::removeSprite")] + #[inline(always)] + fn remove_sprite(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) { *sys::api!(sprite.removeSprite) } + + + /// Returns [`sys::ffi::playdate_sprite::removeSprites`]. + #[doc(alias = "sys::ffi::playdate_sprite::removeSprites")] + #[inline(always)] + fn remove_sprites(&self) -> unsafe extern "C" fn(sprites: *mut *mut LCDSprite, count: c_int) { + *sys::api!(sprite.removeSprites) + } + + + /// Returns [`sys::ffi::playdate_sprite::removeAllSprites`]. + #[doc(alias = "sys::ffi::playdate_sprite::removeAllSprites")] + #[inline(always)] + fn remove_all_sprites(&self) -> unsafe extern "C" fn() { *sys::api!(sprite.removeAllSprites) } + + + /// Returns [`sys::ffi::playdate_sprite::getSpriteCount`]. + #[doc(alias = "sys::ffi::playdate_sprite::getSpriteCount")] + #[inline(always)] + fn get_sprite_count(&self) -> unsafe extern "C" fn() -> c_int { *sys::api!(sprite.getSpriteCount) } + + + /// Returns [`sys::ffi::playdate_sprite::setBounds`]. + #[doc(alias = "sys::ffi::playdate_sprite::setBounds")] + #[inline(always)] + fn set_bounds(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, bounds: PDRect) { + *sys::api!(sprite.setBounds) + } + + + /// Returns [`sys::ffi::playdate_sprite::getBounds`]. + #[doc(alias = "sys::ffi::playdate_sprite::getBounds")] + #[inline(always)] + fn get_bounds(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> PDRect { *sys::api!(sprite.getBounds) } + + + /// Returns [`sys::ffi::playdate_sprite::moveTo`]. + #[doc(alias = "sys::ffi::playdate_sprite::moveTo")] + #[inline(always)] + fn move_to(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, x: c_float, y: c_float) { + *sys::api!(sprite.moveTo) + } + + + /// Returns [`sys::ffi::playdate_sprite::moveBy`]. + #[doc(alias = "sys::ffi::playdate_sprite::moveBy")] + #[inline(always)] + fn move_by(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, dx: c_float, dy: c_float) { + *sys::api!(sprite.moveBy) + } + + + /// Returns [`sys::ffi::playdate_sprite::setImage`]. + #[doc(alias = "sys::ffi::playdate_sprite::setImage")] + #[inline(always)] + fn set_image(&self) + -> unsafe extern "C" fn(sprite: *mut LCDSprite, image: *mut LCDBitmap, flip: LCDBitmapFlip) { + *sys::api!(sprite.setImage) + } + + + /// Returns [`sys::ffi::playdate_sprite::getImage`]. + #[doc(alias = "sys::ffi::playdate_sprite::getImage")] + #[inline(always)] + fn get_image(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> *mut LCDBitmap { + *sys::api!(sprite.getImage) + } + + + /// Returns [`sys::ffi::playdate_sprite::setSize`]. + #[doc(alias = "sys::ffi::playdate_sprite::setSize")] + #[inline(always)] + fn set_size(&self) -> unsafe extern "C" fn(s: *mut LCDSprite, width: c_float, height: c_float) { + *sys::api!(sprite.setSize) + } + + + /// Returns [`sys::ffi::playdate_sprite::setZIndex`]. + #[doc(alias = "sys::ffi::playdate_sprite::setZIndex")] + #[inline(always)] + fn set_z_index(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, zIndex: i16) { + *sys::api!(sprite.setZIndex) + } + + + /// Returns [`sys::ffi::playdate_sprite::getZIndex`]. + #[doc(alias = "sys::ffi::playdate_sprite::getZIndex")] + #[inline(always)] + fn get_z_index(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> i16 { *sys::api!(sprite.getZIndex) } + + + /// Returns [`sys::ffi::playdate_sprite::setDrawMode`]. + #[doc(alias = "sys::ffi::playdate_sprite::setDrawMode")] + #[inline(always)] + fn set_draw_mode(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, mode: LCDBitmapDrawMode) { + *sys::api!(sprite.setDrawMode) + } + + + /// Returns [`sys::ffi::playdate_sprite::setImageFlip`]. + #[doc(alias = "sys::ffi::playdate_sprite::setImageFlip")] + #[inline(always)] + fn set_image_flip(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, flip: LCDBitmapFlip) { + *sys::api!(sprite.setImageFlip) + } + + + /// Returns [`sys::ffi::playdate_sprite::getImageFlip`]. + #[doc(alias = "sys::ffi::playdate_sprite::getImageFlip")] + #[inline(always)] + fn get_image_flip(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> LCDBitmapFlip { + *sys::api!(sprite.getImageFlip) + } + + + /// Returns [`sys::ffi::playdate_sprite::setStencil`]. + #[doc(alias = "sys::ffi::playdate_sprite::setStencil")] + #[inline(always)] + fn set_stencil(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, stencil: *mut LCDBitmap) { + *sys::api!(sprite.setStencil) + } + + + /// Returns [`sys::ffi::playdate_sprite::setClipRect`]. + #[doc(alias = "sys::ffi::playdate_sprite::setClipRect")] + #[inline(always)] + fn set_clip_rect(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, clipRect: LCDRect) { + *sys::api!(sprite.setClipRect) + } + + + /// Returns [`sys::ffi::playdate_sprite::clearClipRect`]. + #[doc(alias = "sys::ffi::playdate_sprite::clearClipRect")] + #[inline(always)] + fn clear_clip_rect(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) { *sys::api!(sprite.clearClipRect) } + + + /// Returns [`sys::ffi::playdate_sprite::setClipRectsInRange`]. + #[doc(alias = "sys::ffi::playdate_sprite::setClipRectsInRange")] + #[inline(always)] + fn set_clip_rects_in_range(&self) -> unsafe extern "C" fn(clipRect: LCDRect, startZ: c_int, endZ: c_int) { + *sys::api!(sprite.setClipRectsInRange) + } + + + /// Returns [`sys::ffi::playdate_sprite::clearClipRectsInRange`]. + #[doc(alias = "sys::ffi::playdate_sprite::clearClipRectsInRange")] + #[inline(always)] + fn clear_clip_rects_in_range(&self) -> unsafe extern "C" fn(startZ: c_int, endZ: c_int) { + *sys::api!(sprite.clearClipRectsInRange) + } + + + /// Returns [`sys::ffi::playdate_sprite::setUpdatesEnabled`]. + #[doc(alias = "sys::ffi::playdate_sprite::setUpdatesEnabled")] + #[inline(always)] + fn set_updates_enabled(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, flag: c_int) { + *sys::api!(sprite.setUpdatesEnabled) + } + + + /// Returns [`sys::ffi::playdate_sprite::updatesEnabled`]. + #[doc(alias = "sys::ffi::playdate_sprite::updatesEnabled")] + #[inline(always)] + fn updates_enabled(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> c_int { + *sys::api!(sprite.updatesEnabled) + } + + + /// Returns [`sys::ffi::playdate_sprite::setCollisionsEnabled`]. + #[doc(alias = "sys::ffi::playdate_sprite::setCollisionsEnabled")] + #[inline(always)] + fn set_collisions_enabled(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, flag: c_int) { + *sys::api!(sprite.setCollisionsEnabled) + } + + + /// Returns [`sys::ffi::playdate_sprite::collisionsEnabled`]. + #[doc(alias = "sys::ffi::playdate_sprite::collisionsEnabled")] + #[inline(always)] + fn collisions_enabled(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> c_int { + *sys::api!(sprite.collisionsEnabled) + } + + + /// Returns [`sys::ffi::playdate_sprite::setVisible`]. + #[doc(alias = "sys::ffi::playdate_sprite::setVisible")] + #[inline(always)] + fn set_visible(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, flag: c_int) { + *sys::api!(sprite.setVisible) + } + + + /// Returns [`sys::ffi::playdate_sprite::isVisible`]. + #[doc(alias = "sys::ffi::playdate_sprite::isVisible")] + #[inline(always)] + fn is_visible(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> c_int { *sys::api!(sprite.isVisible) } + + + /// Returns [`sys::ffi::playdate_sprite::setOpaque`]. + #[doc(alias = "sys::ffi::playdate_sprite::setOpaque")] + #[inline(always)] + fn set_opaque(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, flag: c_int) { + *sys::api!(sprite.setOpaque) + } + + + /// Returns [`sys::ffi::playdate_sprite::markDirty`]. + #[doc(alias = "sys::ffi::playdate_sprite::markDirty")] + #[inline(always)] + fn mark_dirty(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) { *sys::api!(sprite.markDirty) } + + + /// Returns [`sys::ffi::playdate_sprite::setTag`]. + #[doc(alias = "sys::ffi::playdate_sprite::setTag")] + #[inline(always)] + fn set_tag(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, tag: u8) { *sys::api!(sprite.setTag) } + + + /// Returns [`sys::ffi::playdate_sprite::getTag`]. + #[doc(alias = "sys::ffi::playdate_sprite::getTag")] + #[inline(always)] + fn get_tag(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> u8 { *sys::api!(sprite.getTag) } + + + /// Returns [`sys::ffi::playdate_sprite::setIgnoresDrawOffset`]. + #[doc(alias = "sys::ffi::playdate_sprite::setIgnoresDrawOffset")] + #[inline(always)] + fn set_ignores_draw_offset(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, flag: c_int) { + *sys::api!(sprite.setIgnoresDrawOffset) + } + + + /// Returns [`sys::ffi::playdate_sprite::setUpdateFunction`]. + #[doc(alias = "sys::ffi::playdate_sprite::setUpdateFunction")] + #[inline(always)] + fn set_update_function(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, func: LCDSpriteUpdateFunction) { + *sys::api!(sprite.setUpdateFunction) + } + + + /// Returns [`sys::ffi::playdate_sprite::setDrawFunction`]. + #[doc(alias = "sys::ffi::playdate_sprite::setDrawFunction")] + #[inline(always)] + fn set_draw_function(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, func: LCDSpriteDrawFunction) { + *sys::api!(sprite.setDrawFunction) + } + + + /// Returns [`sys::ffi::playdate_sprite::getPosition`]. + #[doc(alias = "sys::ffi::playdate_sprite::getPosition")] + #[inline(always)] + fn get_position(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, x: *mut c_float, y: *mut c_float) { + *sys::api!(sprite.getPosition) + } + + + /// Returns [`sys::ffi::playdate_sprite::resetCollisionWorld`]. + #[doc(alias = "sys::ffi::playdate_sprite::resetCollisionWorld")] + #[inline(always)] + fn reset_collision_world(&self) -> unsafe extern "C" fn() { *sys::api!(sprite.resetCollisionWorld) } + + + /// Returns [`sys::ffi::playdate_sprite::setCollideRect`]. + #[doc(alias = "sys::ffi::playdate_sprite::setCollideRect")] + #[inline(always)] + fn set_collide_rect(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, collideRect: PDRect) { + *sys::api!(sprite.setCollideRect) + } + + + /// Returns [`sys::ffi::playdate_sprite::getCollideRect`]. + #[doc(alias = "sys::ffi::playdate_sprite::getCollideRect")] + #[inline(always)] + fn get_collide_rect(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> PDRect { + *sys::api!(sprite.getCollideRect) + } + + + /// Returns [`sys::ffi::playdate_sprite::clearCollideRect`]. + #[doc(alias = "sys::ffi::playdate_sprite::clearCollideRect")] + #[inline(always)] + fn clear_collide_rect(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) { + *sys::api!(sprite.clearCollideRect) + } + + + /// Returns [`sys::ffi::playdate_sprite::setCollisionResponseFunction`]. + #[doc(alias = "sys::ffi::playdate_sprite::setCollisionResponseFunction")] + #[inline(always)] + fn set_collision_response_function( + &self) + -> unsafe extern "C" fn(sprite: *mut LCDSprite, func: LCDSpriteCollisionFilterProc) { + *sys::api!(sprite.setCollisionResponseFunction) + } + + + /// Returns [`sys::ffi::playdate_sprite::checkCollisions`]. + #[doc(alias = "sys::ffi::playdate_sprite::checkCollisions")] + #[inline(always)] + fn check_collisions( + &self) + -> unsafe extern "C" fn(sprite: *mut LCDSprite, + goalX: c_float, + goalY: c_float, + actualX: *mut c_float, + actualY: *mut c_float, + len: *mut c_int) -> *mut SpriteCollisionInfo { + *sys::api!(sprite.checkCollisions) + } + + + /// Returns [`sys::ffi::playdate_sprite::moveWithCollisions`]. + #[doc(alias = "sys::ffi::playdate_sprite::moveWithCollisions")] + #[inline(always)] + fn move_with_collisions( + &self) + -> unsafe extern "C" fn(sprite: *mut LCDSprite, + goalX: c_float, + goalY: c_float, + actualX: *mut c_float, + actualY: *mut c_float, + len: *mut c_int) -> *mut SpriteCollisionInfo { + *sys::api!(sprite.moveWithCollisions) + } + + + /// Returns [`sys::ffi::playdate_sprite::querySpritesAtPoint`]. + #[doc(alias = "sys::ffi::playdate_sprite::querySpritesAtPoint")] + #[inline(always)] + fn query_sprites_at_point( + &self) + -> unsafe extern "C" fn(x: c_float, y: c_float, len: *mut c_int) -> *mut *mut LCDSprite { + *sys::api!(sprite.querySpritesAtPoint) + } + + + /// Returns [`sys::ffi::playdate_sprite::querySpritesInRect`]. + #[doc(alias = "sys::ffi::playdate_sprite::querySpritesInRect")] + #[inline(always)] + fn query_sprites_in_rect( + &self) + -> unsafe extern "C" fn(x: c_float, + y: c_float, + width: c_float, + height: c_float, + len: *mut c_int) -> *mut *mut LCDSprite { + *sys::api!(sprite.querySpritesInRect) + } + + + /// Returns [`sys::ffi::playdate_sprite::querySpritesAlongLine`]. + #[doc(alias = "sys::ffi::playdate_sprite::querySpritesAlongLine")] + #[inline(always)] + fn query_sprites_along_line( + &self) + -> unsafe extern "C" fn(x1: c_float, + y1: c_float, + x2: c_float, + y2: c_float, + len: *mut c_int) -> *mut *mut LCDSprite { + *sys::api!(sprite.querySpritesAlongLine) + } + + + /// Returns [`sys::ffi::playdate_sprite::querySpriteInfoAlongLine`]. + #[doc(alias = "sys::ffi::playdate_sprite::querySpriteInfoAlongLine")] + #[inline(always)] + fn query_sprite_info_along_line( + &self) + -> unsafe extern "C" fn(x1: c_float, + y1: c_float, + x2: c_float, + y2: c_float, + len: *mut c_int) -> *mut SpriteQueryInfo { + *sys::api!(sprite.querySpriteInfoAlongLine) + } + + + /// Returns [`sys::ffi::playdate_sprite::overlappingSprites`]. + #[doc(alias = "sys::ffi::playdate_sprite::overlappingSprites")] + #[inline(always)] + fn overlapping_sprites( + &self) + -> unsafe extern "C" fn(sprite: *mut LCDSprite, len: *mut c_int) -> *mut *mut LCDSprite { + *sys::api!(sprite.overlappingSprites) + } + + + /// Returns [`sys::ffi::playdate_sprite::allOverlappingSprites`]. + #[doc(alias = "sys::ffi::playdate_sprite::allOverlappingSprites")] + #[inline(always)] + fn all_overlapping_sprites(&self) -> unsafe extern "C" fn(len: *mut c_int) -> *mut *mut LCDSprite { + *sys::api!(sprite.allOverlappingSprites) + } + + + /// Returns [`sys::ffi::playdate_sprite::setStencilPattern`]. + #[doc(alias = "sys::ffi::playdate_sprite::setStencilPattern")] + #[inline(always)] + fn set_stencil_pattern(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, pattern: *mut [u8; 8]) { + *sys::api!(sprite.setStencilPattern) + } + + + /// Returns [`sys::ffi::playdate_sprite::clearStencil`]. + #[doc(alias = "sys::ffi::playdate_sprite::clearStencil")] + #[inline(always)] + fn clear_stencil(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) { *sys::api!(sprite.clearStencil) } + + + /// Returns [`sys::ffi::playdate_sprite::setUserdata`]. + #[doc(alias = "sys::ffi::playdate_sprite::setUserdata")] + #[inline(always)] + fn set_userdata(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite, userdata: *mut c_void) { + *sys::api!(sprite.setUserdata) + } + + + /// Returns [`sys::ffi::playdate_sprite::getUserdata`]. + #[doc(alias = "sys::ffi::playdate_sprite::getUserdata")] + #[inline(always)] + fn get_userdata(&self) -> unsafe extern "C" fn(sprite: *mut LCDSprite) -> *mut c_void { + *sys::api!(sprite.getUserdata) + } + + + /// Returns [`sys::ffi::playdate_sprite::setStencilImage`]. + #[doc(alias = "sys::ffi::playdate_sprite::setStencilImage")] + #[inline(always)] + fn set_stencil_image(&self) + -> unsafe extern "C" fn(sprite: *mut LCDSprite, stencil: *mut LCDBitmap, tile: c_int) { + *sys::api!(sprite.setStencilImage) + } +} diff --git a/api/sprite/src/callback/collision.rs b/api/sprite/src/callback/collision.rs new file mode 100644 index 00000000..36a88d01 --- /dev/null +++ b/api/sprite/src/callback/collision.rs @@ -0,0 +1,298 @@ +use core::marker::PhantomData; +use core::ops::Deref; + +use sys::ffi::{LCDSprite, SpriteCollisionResponseType}; +use sys::traits::AsRaw; + +use crate::{Sprite, SpriteApi, TypedSprite, SpriteRef, AnySprite, SharedSprite}; +use crate::api; + + +// This is mostly probably should be implemented for OwnedSprite only. +impl Sprite { + /// Sets the collision response function for the this sprite. + pub fn into_collision_response_handler>(self) -> Handle + where T::Api: Default { + Handle::new(self) + } +} + + +pub trait SpriteCollisionResponse: Sized + TypedSprite + where Self: From { + fn on_collision(sprite: &Handle, Self>, + other: SpriteRef) + -> SpriteCollisionResponseType; + + unsafe extern "C" fn proxy(sprite: *mut LCDSprite, other: *mut LCDSprite) -> SpriteCollisionResponseType + where Self::Api: Default { + sys::println!("on_collision"); + Self::on_collision( + &Handle::new_unchanged(SpriteRef::from(sprite).into()), + SpriteRef::from(other), + ) + } +} + + +pub struct Handle(pub(super) T, PhantomData) + where T: TypedSprite + Sized, + H: SpriteCollisionResponse; + +impl AnySprite for Handle + where T: TypedSprite + Sized, + H: SpriteCollisionResponse, + T: AnySprite +{ +} +impl SpriteApi for Handle + where T: TypedSprite + Sized, + H: SpriteCollisionResponse, + T: SpriteApi +{ + type Api = ::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { self.0.api_ref() } +} + +impl AsRaw for Handle + where T: TypedSprite + Sized, + H: SpriteCollisionResponse, + T: AsRaw +{ + type Type = ::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } +} + +impl Handle + where T: TypedSprite + Sized, + H: SpriteCollisionResponse +{ + /// - Unregister inner callback for sprite + /// - Unwrap, return the underlying sprite + /// - Remove the collision response handler + #[must_use = "Sprite"] + pub fn into_inner(self) -> T { + use api::Api; + + let ptr = unsafe { self.0.as_raw() }; + let f = self.0.api_ref().set_collision_response_function(); + unsafe { f(ptr, None) }; + self.0 + } +} + + +impl AsRef> for Handle + where T: TypedSprite + AsRef>, + H: SpriteCollisionResponse +{ + fn as_ref(&self) -> &Sprite { self.0.as_ref() } +} + + +impl AsMut> for Handle + where T: TypedSprite + AsMut>, + H: SpriteCollisionResponse +{ + fn as_mut(&mut self) -> &mut Sprite { self.0.as_mut() } +} + + +impl Deref for Handle + where T: TypedSprite + AsRef>, + H: SpriteCollisionResponse +{ + type Target = Sprite; + fn deref(&self) -> &Self::Target { self.0.as_ref() } +} + + +impl Handle + where T: TypedSprite + SpriteApi, + H: SpriteCollisionResponse +{ + pub(super) fn new(sprite: T) -> Self + where H::Api: Default { + use crate::api::Api; + + let f = sprite.api_ref().set_collision_response_function(); + unsafe { f(sprite.as_raw(), Some(H::proxy)) }; + Self::new_unchanged(sprite) + } + + fn new_unchanged(sprite: T) -> Self { Self(sprite, PhantomData::) } +} + + +pub(crate) mod l2 { + use core::ops::Deref; + use core::marker::PhantomData; + + use sys::traits::AsRaw; + + use crate::AnySprite; + use crate::Sprite; + use crate::SpriteApi; + use crate::TypedSprite; + use crate::api; + use crate::callback::draw; + use crate::callback::update; + + use super::SpriteCollisionResponse; + + + impl update::Handle, H> + where Api: api::Api, + H: update::SpriteUpdate + { + /// Sets the collision response function for the this sprite. + pub fn into_collision_response_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl draw::Handle, H> + where Api: api::Api, + H: draw::SpriteDraw + { + /// Sets the collision response function for the this sprite. + pub fn into_collision_response_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl + draw::l2::Handle, update::Handle, H0>, H> + where Api: api::Api, + H: draw::SpriteDraw, + H0: update::SpriteUpdate + { + /// Sets the collision response function for the this sprite. + pub fn into_collision_response_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl + update::l2::Handle, draw::Handle, H0>, H> + where Api: api::Api, + H: update::SpriteUpdate, + H0: draw::SpriteDraw + { + /// Sets the collision response function for the this sprite. + pub fn into_collision_response_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + + pub struct Handle(pub(super) T, PhantomData, PhantomData) + where T: Sized, + Sp: TypedSprite, + H: SpriteCollisionResponse; + + impl AnySprite for Handle + where Sp: TypedSprite, + T: AnySprite, + H: SpriteCollisionResponse + { + } + impl SpriteApi for Handle + where Sp: TypedSprite, + T: SpriteApi, + H: SpriteCollisionResponse + { + type Api = ::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { self.0.api_ref() } + } + + impl AsRaw for Handle + where Sp: TypedSprite, + T: AsRaw, + H: SpriteCollisionResponse + { + type Type = ::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } + } + + + impl Handle + where T: AsRef, + Sp: TypedSprite, + H: SpriteCollisionResponse + { + /// - Unregister inner callback for sprite + /// - Unwrap, return the underlying sprite + /// - Remove the collision response handler + #[must_use = "Sprite"] + pub fn into_inner(self) -> T { + use crate::api::Api; + + let ptr = unsafe { self.0.as_ref().as_raw() }; + let f = self.0.as_ref().api_ref().set_collision_response_function(); + unsafe { f(ptr, None) }; + self.0 + } + } + + + impl AsRef> for Handle + where T: AsRef>, + Sp: TypedSprite, + H: SpriteCollisionResponse + { + fn as_ref(&self) -> &Sprite { self.0.as_ref() } + } + + + impl Deref for Handle + where T: AsRef>, + Sp: TypedSprite, + H: SpriteCollisionResponse + { + type Target = Sprite; + fn deref(&self) -> &Self::Target { self.0.as_ref() } + } + + + impl Handle + where T: AsRef, + Sp: TypedSprite + SpriteApi, + H: SpriteCollisionResponse + { + pub(super) fn new(sprite: T) -> Self + where H::Api: Default { + use crate::api::Api; + + let f = sprite.as_ref().api_ref().set_collision_response_function(); + unsafe { f(sprite.as_ref().as_raw(), Some(H::proxy)) }; + Self::new_unchanged(sprite) + } + + fn new_unchanged(sprite: T) -> Self { Self(sprite, PhantomData::, PhantomData::) } + } +} diff --git a/api/sprite/src/callback/draw.rs b/api/sprite/src/callback/draw.rs new file mode 100644 index 00000000..4d5e77f8 --- /dev/null +++ b/api/sprite/src/callback/draw.rs @@ -0,0 +1,307 @@ +use core::marker::PhantomData; +use core::ops::Deref; + +use sys::ffi::{LCDSprite, PDRect}; +use sys::traits::AsRaw; + +use crate::{Sprite, SpriteApi, TypedSprite, SpriteRef, AnySprite, SharedSprite}; +use crate::api::{self, Api}; + + +// This is mostly probably should be implemented for OwnedSprite only. +impl Sprite { + /// Sets the draw function for the this sprite. + /// + /// ⚠️ Caution: Using with [`Sprite::set_image`], + /// do not forget to set image __before__ setting draw function, + /// but not __after__. + pub fn into_draw_handler>(self) -> Handle + where T::Api: Default { + Handle::new(self) + } +} + + +pub trait SpriteDraw: Sized + TypedSprite + where Self: From { + fn on_draw(sprite: &Handle, Self>, + bounds: PDRect, + draw_rect: PDRect); + + unsafe extern "C" fn proxy(sprite: *mut LCDSprite, bounds: PDRect, draw_rect: PDRect) + where Self::Api: Default { + Self::on_draw( + &Handle::new_unchanged(SpriteRef::from(sprite).into()), + bounds, + draw_rect, + ) + } +} + + +pub struct Handle(pub(super) T, PhantomData) + where T: TypedSprite + Sized, + H: SpriteDraw; + +impl AnySprite for Handle + where T: TypedSprite + Sized, + H: SpriteDraw, + T: AnySprite +{ +} +impl SpriteApi for Handle + where T: TypedSprite + Sized, + H: SpriteDraw, + T: SpriteApi +{ + type Api = ::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { self.0.api_ref() } +} + +impl AsRaw for Handle + where T: TypedSprite + Sized, + H: SpriteDraw, + T: AsRaw +{ + type Type = ::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } +} + +impl Handle + where T: TypedSprite + Sized, + H: SpriteDraw +{ + /// - Unregister inner callback for sprite + /// - Unwrap, return the underlying sprite + /// - Remove the draw handler + #[must_use = "Sprite"] + pub fn into_inner(self) -> T { + let ptr = unsafe { self.0.as_raw() }; + let f = self.0.api_ref().set_draw_function(); + unsafe { f(ptr, None) }; + self.0 + } +} + + +impl AsRef> for Handle + where T: TypedSprite + AsRef>, + H: SpriteDraw +{ + fn as_ref(&self) -> &Sprite { self.0.as_ref() } +} + + +impl AsMut> for Handle + where T: TypedSprite + AsMut>, + H: SpriteDraw +{ + fn as_mut(&mut self) -> &mut Sprite { self.0.as_mut() } +} + + +impl Deref for Handle + where T: TypedSprite + AsRef>, + H: SpriteDraw +{ + type Target = Sprite; + fn deref(&self) -> &Self::Target { self.0.as_ref() } +} + + +impl Handle + where T: TypedSprite + SpriteApi, + H: SpriteDraw +{ + pub(super) fn new(sprite: T) -> Self + where H::Api: Default { + let f = sprite.api_ref().set_draw_function(); + unsafe { f(sprite.as_raw(), Some(H::proxy)) }; + Self::new_unchanged(sprite) + } + + fn new_unchanged(sprite: T) -> Self { Self(sprite, PhantomData::) } +} + + +pub(crate) mod l2 { + use core::ops::Deref; + use core::marker::PhantomData; + + use sys::traits::AsRaw; + + use crate::AnySprite; + use crate::Sprite; + use crate::SpriteApi; + use crate::TypedSprite; + use crate::api; + use crate::callback::update; + use crate::callback::collision; + + use super::SpriteDraw; + + + impl update::Handle, H> + where Api: api::Api, + H: update::SpriteUpdate + { + /// Sets the draw function for the this sprite. + /// + /// ⚠️ Caution: Using with [`Sprite::set_image`], + /// Do not forget to set image __before__ setting draw function, + /// but not __after__. + pub fn into_draw_handler>(self) -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl collision::Handle, H> + where Api: api::Api, + H: collision::SpriteCollisionResponse + { + /// Sets the draw function for the this sprite. + /// + /// ⚠️ Caution: Using with [`Sprite::set_image`], + /// Do not forget to set image __before__ setting draw function, + /// but not __after__. + pub fn into_draw_handler>(self) -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl + collision::l2::Handle, update::Handle, H0>, H> + where Api: api::Api, + H: collision::SpriteCollisionResponse, + H0: update::SpriteUpdate + { + /// Sets the draw function for the this sprite. + /// + /// ⚠️ Caution: Using with [`Sprite::set_image`], + /// Do not forget to set image __before__ setting draw function, + /// but not __after__. + pub fn into_draw_handler>(self) -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl + update::l2::Handle, collision::Handle, H0>, H> + where Api: api::Api, + H: update::SpriteUpdate, + H0: collision::SpriteCollisionResponse + { + /// Sets the draw function for the this sprite. + /// + /// ⚠️ Caution: Using with [`Sprite::set_image`], + /// Do not forget to set image __before__ setting draw function, + /// but not __after__. + pub fn into_draw_handler>(self) -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + + pub struct Handle(pub(super) T, PhantomData, PhantomData) + where T: Sized, + Sp: TypedSprite, + H: SpriteDraw; + + + impl AnySprite for Handle + where Sp: TypedSprite, + T: AnySprite, + H: SpriteDraw + { + } + impl SpriteApi for Handle + where Sp: TypedSprite, + T: SpriteApi, + H: SpriteDraw + { + type Api = ::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { self.0.api_ref() } + } + + impl AsRaw for Handle + where Sp: TypedSprite, + T: AsRaw, + H: SpriteDraw + { + type Type = ::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } + } + + + impl Handle + where T: AsRef, + Sp: TypedSprite, + H: SpriteDraw + { + /// - Unregister inner callback for sprite + /// - Unwrap, return the underlying sprite + /// - Remove the collision response handler + #[must_use = "Sprite"] + pub fn into_inner(self) -> T { + use crate::api::Api; + + let ptr = unsafe { self.0.as_ref().as_raw() }; + let f = self.0.as_ref().api_ref().set_draw_function(); + unsafe { f(ptr, None) }; + self.0 + } + } + + + impl AsRef> for Handle + where T: AsRef>, + Sp: TypedSprite, + H: SpriteDraw + { + fn as_ref(&self) -> &Sprite { self.0.as_ref() } + } + + + impl Deref for Handle + where T: AsRef>, + Sp: TypedSprite, + H: SpriteDraw + { + type Target = Sprite; + fn deref(&self) -> &Self::Target { self.0.as_ref() } + } + + + impl Handle + where T: AsRef, + Sp: TypedSprite + SpriteApi, + H: SpriteDraw + { + pub(super) fn new(sprite: T) -> Self + where H::Api: Default { + use crate::api::Api; + + let f = sprite.as_ref().api_ref().set_draw_function(); + unsafe { f(sprite.as_ref().as_raw(), Some(H::proxy)) }; + Self::new_unchanged(sprite) + } + + fn new_unchanged(sprite: T) -> Self { Self(sprite, PhantomData::, PhantomData::) } + } +} diff --git a/api/sprite/src/callback/func.rs b/api/sprite/src/callback/func.rs new file mode 100644 index 00000000..0ede652d --- /dev/null +++ b/api/sprite/src/callback/func.rs @@ -0,0 +1,96 @@ +//! WiP + +use core::ops::Deref; +use core::pin::Pin; + +use sys::traits::AsRaw; +use sys::ffi::LCDSprite; + +use crate::AnySprite; +use crate::Sprite; +use crate::SpriteApi; +use crate::SpriteRef; +use crate::api; + + +impl Sprite { + /// Wraps into sprite that supports fn callbacks. + /// + /// But this doesn’t supports static stateless handlers. + pub fn with_callbacks(self) -> SpriteCb { SpriteCb::new(self) } +} + + +#[derive(Debug)] +#[repr(transparent)] +pub struct SpriteCb(Sprite); + + +impl SpriteCb { + fn new(sprite: Sprite) -> Self { + // reassign userdata + let ud = sprite.take_userdata(); + // TODO: init ud layout/structure. set ud into it. + + sprite.set_userdata(*ud.unwrap()); + // + todo!() + } + + // TODO: override get/set/take_userdata methods + + + pub fn set_update_function(&self, on_update: Pin) + where F: FnMut(SpriteRef) { + let f = self.0.api_ref().set_update_function(); + // unsafe { f(self.0.as_raw(), ) } + } + + + pub fn set_draw_function(&self) { + let f = self.0.api_ref().set_draw_function(); + // unsafe { f(self.0.as_raw(), ) } + } + + pub fn set_collision_response_function(&self) { + let f = self.0.api_ref().set_collision_response_function(); + // unsafe { f(self.0.as_raw(), ) } + todo!() + } +} + + +impl Deref for SpriteCb { + type Target = Sprite; + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl AnySprite for SpriteCb + where SpriteCb: AnySprite +{ +} + +impl SpriteApi for SpriteCb + where SpriteCb: SpriteApi, + SpriteCb: SpriteApi +{ + type Api = as SpriteApi>::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + use api::Api; + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { + use api::Api; + self.0.api_ref() + } +} + +impl AsRaw for SpriteCb + where SpriteCb: AsRaw +{ + type Type = as AsRaw>::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } +} diff --git a/api/sprite/src/callback/update.rs b/api/sprite/src/callback/update.rs new file mode 100644 index 00000000..e2a6fb2d --- /dev/null +++ b/api/sprite/src/callback/update.rs @@ -0,0 +1,289 @@ +use core::marker::PhantomData; +use core::ops::Deref; + +use sys::ffi::LCDSprite; +use sys::traits::AsRaw; + +use crate::{Sprite, SpriteApi, TypedSprite, SpriteRef, AnySprite, SharedSprite}; +use crate::api::{self, Api}; + + +// This is mostly probably should be implemented for OwnedSprite only. +impl Sprite { + /// Sets the update function for the this sprite. + pub fn into_update_handler>(self) -> Handle + where T::Api: Default { + Handle::new(self) + } +} + + +pub trait SpriteUpdate: Sized + TypedSprite + where Self: From { + fn on_update(sprite: &Handle, Self>); + + unsafe extern "C" fn proxy(sprite: *mut LCDSprite) + where Self::Api: Default { + Self::on_update(&Handle::new_unchanged(SpriteRef::from(sprite).into())) + } +} + + +pub struct Handle(pub(super) T, PhantomData) + where T: TypedSprite + Sized, + H: SpriteUpdate; + +impl AnySprite for Handle + where T: TypedSprite + Sized, + H: SpriteUpdate, + T: AnySprite +{ +} +impl SpriteApi for Handle + where T: TypedSprite + Sized, + H: SpriteUpdate, + T: SpriteApi +{ + type Api = ::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { self.0.api_ref() } +} + +impl AsRaw for Handle + where T: TypedSprite + Sized, + H: SpriteUpdate, + T: AsRaw +{ + type Type = ::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } +} + +impl Handle + where T: TypedSprite + Sized, + H: SpriteUpdate +{ + /// - Unregister inner callback for sprite + /// - Unwrap, return the underlying sprite + /// - Remove the update handler + #[must_use = "Sprite"] + pub fn into_inner(self) -> T { + let ptr = unsafe { self.0.as_raw() }; + let f = self.0.api_ref().set_update_function(); + unsafe { f(ptr, None) }; + self.0 + } +} + + +impl AsRef> for Handle + where T: TypedSprite + AsRef>, + H: SpriteUpdate +{ + fn as_ref(&self) -> &Sprite { self.0.as_ref() } +} + + +impl AsMut> for Handle + where T: TypedSprite + AsMut>, + H: SpriteUpdate +{ + fn as_mut(&mut self) -> &mut Sprite { self.0.as_mut() } +} + + +impl Deref for Handle + where T: TypedSprite + AsRef>, + H: SpriteUpdate +{ + type Target = Sprite; + fn deref(&self) -> &Self::Target { self.0.as_ref() } +} + + +impl Handle + where T: TypedSprite + SpriteApi, + H: SpriteUpdate +{ + pub(super) fn new(sprite: T) -> Self + where H::Api: Default { + let f = sprite.api_ref().set_update_function(); + unsafe { f(sprite.as_raw(), Some(H::proxy)) }; + Self::new_unchanged(sprite) + } + + fn new_unchanged(sprite: T) -> Self { Self(sprite, PhantomData::) } +} + + +pub(crate) mod l2 { + use core::ops::Deref; + use core::marker::PhantomData; + + use sys::traits::AsRaw; + + use crate::AnySprite; + use crate::Sprite; + use crate::SpriteApi; + use crate::TypedSprite; + use crate::api; + use crate::callback::draw; + use crate::callback::collision; + + use super::SpriteUpdate; + + + impl collision::Handle, H> + where Api: api::Api, + H: collision::SpriteCollisionResponse + { + /// Sets the update function for the this sprite. + pub fn into_update_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl draw::Handle, H> + where Api: api::Api, + H: draw::SpriteDraw + { + /// Sets the update function for the this sprite. + pub fn into_update_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl + draw::l2::Handle, collision::Handle, H0>, H> + where Api: api::Api, + H: draw::SpriteDraw, + H0: collision::SpriteCollisionResponse + { + /// Sets the update function for the this sprite. + pub fn into_update_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + impl + collision::l2::Handle, draw::Handle, H0>, H> + where Api: api::Api, + H: collision::SpriteCollisionResponse, + H0: draw::SpriteDraw + { + /// Sets the update function for the this sprite. + pub fn into_update_handler>( + self) + -> Handle, Self, T> + where T::Api: Default { + Handle::new(self) + } + } + + + pub struct Handle(pub(super) T, PhantomData, PhantomData) + where T: Sized, + Sp: TypedSprite, + H: SpriteUpdate; + + + impl AnySprite for Handle + where Sp: TypedSprite, + T: AnySprite, + H: SpriteUpdate + { + } + impl SpriteApi for Handle + where Sp: TypedSprite, + T: SpriteApi, + H: SpriteUpdate + { + type Api = ::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + self.0.api() + } + + fn api_ref(&self) -> &Self::Api { self.0.api_ref() } + } + + impl AsRaw for Handle + where Sp: TypedSprite, + T: AsRaw, + H: SpriteUpdate + { + type Type = ::Type; + unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() } + } + + + impl Handle + where T: AsRef, + Sp: TypedSprite, + H: SpriteUpdate + { + /// - Unregister inner callback for sprite + /// - Unwrap, return the underlying sprite + /// - Remove the collision response handler + #[must_use = "Sprite"] + pub fn into_inner(self) -> T { + use crate::api::Api; + + let ptr = unsafe { self.0.as_ref().as_raw() }; + let f = self.0.as_ref().api_ref().set_update_function(); + unsafe { f(ptr, None) }; + self.0 + } + } + + + impl AsRef> for Handle + where T: AsRef>, + Sp: TypedSprite, + H: SpriteUpdate + { + fn as_ref(&self) -> &Sprite { self.0.as_ref() } + } + + + impl Deref for Handle + where T: AsRef>, + Sp: TypedSprite, + H: SpriteUpdate + { + type Target = Sprite; + fn deref(&self) -> &Self::Target { self.0.as_ref() } + } + + + impl Handle + where T: AsRef, + Sp: TypedSprite + SpriteApi, + H: SpriteUpdate + { + pub(super) fn new(sprite: T) -> Self + where H::Api: Default { + use crate::api::Api; + + let f = sprite.as_ref().api_ref().set_update_function(); + unsafe { f(sprite.as_ref().as_raw(), Some(H::proxy)) }; + Self::new_unchanged(sprite) + } + + fn new_unchanged(sprite: T) -> Self { Self(sprite, PhantomData::, PhantomData::) } + } +} diff --git a/api/sprite/src/ext.rs b/api/sprite/src/ext.rs new file mode 100644 index 00000000..ce5f7daf --- /dev/null +++ b/api/sprite/src/ext.rs @@ -0,0 +1,45 @@ +use core::ffi::c_float; + +use sys::ffi::CollisionPoint; +use sys::ffi::SpriteQueryInfo; + +use crate::SpriteRef; + + +pub trait SpriteQueryInfoExt { + /// The sprite being intersected by the segment + fn sprite(&self) -> SpriteRef; + + /// Entry point + /// + /// `ti1` and `ti2` are numbers between 0 and 1 which indicate how far from the starting point of the line segment the collision happened + fn ti1(&self) -> c_float; + + /// Exit point + /// + /// `ti1` and `ti2` are numbers between 0 and 1 which indicate how far from the starting point of the line segment the collision happened + fn ti2(&self) -> c_float; + + /// The coordinates of the first intersection between sprite and the line segment + fn entry_point(&self) -> &CollisionPoint; + fn entry_point_mut(&mut self) -> &mut CollisionPoint; + + /// The coordinates of the second intersection between sprite and the line segment + fn exit_point(&self) -> &CollisionPoint; + fn exit_point_mut(&mut self) -> &mut CollisionPoint; +} + + +impl SpriteQueryInfoExt for SpriteQueryInfo { + fn sprite(&self) -> SpriteRef { self.sprite.into() } + + fn ti1(&self) -> c_float { self.ti1 } + + fn ti2(&self) -> c_float { self.ti2 } + + fn entry_point(&self) -> &CollisionPoint { &self.entryPoint } + fn entry_point_mut(&mut self) -> &mut CollisionPoint { &mut self.entryPoint } + + fn exit_point(&self) -> &CollisionPoint { &self.exitPoint } + fn exit_point_mut(&mut self) -> &mut CollisionPoint { &mut self.exitPoint } +} diff --git a/api/sprite/src/lib.rs b/api/sprite/src/lib.rs new file mode 100644 index 00000000..ae2f253e --- /dev/null +++ b/api/sprite/src/lib.rs @@ -0,0 +1,273 @@ +#![cfg_attr(not(test), no_std)] + +extern crate sys; +extern crate alloc; + +use core::ffi::c_int; +use core::ffi::c_float; + +use sys::traits::AsRaw; +use sys::ffi::SpriteQueryInfo; +use sys::ffi::LCDRect; +use sys::ffi::LCDSprite; + +pub mod ext; +mod sprite; +pub mod api; + +pub mod callback { + pub mod draw; + pub mod update; + pub mod collision; +} + +pub mod prelude { + pub use super::sprite::*; + pub use super::api::Api as _; + pub use super::api::Default as DefaultSpriteApi; + pub use super::callback::draw::SpriteDraw; + pub use super::callback::update::SpriteUpdate; + pub use super::callback::collision::SpriteCollisionResponse; + + pub use super::{TypedSprite, SpriteApi}; +} + +pub use sprite::*; +use crate::api::Api; + + +/// When flag is set to `true`, the given sprite will always redraw. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::setAlwaysRedraw`] +#[doc(alias = "sys::ffi::playdate_sprite::setAlwaysRedraw")] +pub fn set_always_redraw(value: bool) { + let f = api::Api::set_always_redraw(&api::Default); + unsafe { f(value.into()) } +} + +/// Marks the given dirty_rect (in screen coordinates) as needing a redraw. +/// +/// Graphics drawing functions now call this automatically, +/// adding their drawn areas to the sprite’s dirty list, +/// so there’s usually no need to call this manually. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::addDirtyRect`] +#[doc(alias = "sys::ffi::playdate_sprite::addDirtyRect")] +pub fn add_dirty_rect(rect: LCDRect) { + let f = api::Api::add_dirty_rect(&api::Default); + unsafe { f(rect) } +} + +/// Draws every sprite in the display list. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::drawSprites`] +#[doc(alias = "sys::ffi::playdate_sprite::drawSprites")] +pub fn draw_sprites() { + let f = api::Api::draw_sprites(&api::Default); + unsafe { f() } +} + +/// Updates and draws every sprite in the display list. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::updateAndDrawSprites`] +#[doc(alias = "sys::ffi::playdate_sprite::updateAndDrawSprites")] +pub fn update_and_draw_sprites() { + let f = api::Api::update_and_draw_sprites(&api::Default); + unsafe { f() } +} + + +/// Adds the given sprite to the display list, +/// so that it is drawn in the current scene. +/// +/// See also [`Sprite::add`] +/// +/// Equivalent to [`sys::ffi::playdate_sprite::addSprite`] +#[doc(alias = "sys::ffi::playdate_sprite::addSprite")] +pub fn add_sprite(sprite: &impl AnySprite) { + let f = sprite.api_ref().add_sprite(); + unsafe { f(sprite.as_raw()) } +} + +/// Removes the given sprite from the display list. +/// +/// See also [`Sprite::remove`] +/// +/// Equivalent to [`sys::ffi::playdate_sprite::removeSprite`] +#[doc(alias = "sys::ffi::playdate_sprite::removeSprite")] +pub fn remove_sprite(sprite: &impl AnySprite) { + let f = sprite.api_ref().remove_sprite(); + unsafe { f(sprite.as_raw()) } +} + +/// Removes all of the given sprites from the display list. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::removeSprites`] +#[doc(alias = "sys::ffi::playdate_sprite::removeSprites")] +pub fn remove_sprites(sprites: &[impl AnySprite]) { + let mut ptrs = alloc::vec::Vec::with_capacity(sprites.len()); + ptrs.extend(sprites.into_iter().map(|sp| unsafe { sp.as_raw() })); + let f = sprites.first() + .map(|sp| sp.api_ref().remove_sprites()) + .unwrap_or(api::Default.remove_sprites()); + unsafe { f(ptrs.as_mut_ptr(), sprites.len() as _) } + drop(ptrs); +} + + +/// Removes all sprites from the display list. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::removeAllSprites`] +#[doc(alias = "sys::ffi::playdate_sprite::removeAllSprites")] +pub fn remove_all_sprites() { + let f = api::Api::remove_all_sprites(&api::Default); + unsafe { f() } +} + + +/// Returns the total number of sprites in the display list. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::getSpriteCount`] +#[doc(alias = "sys::ffi::playdate_sprite::getSpriteCount")] +pub fn sprite_count() -> c_int { + let f = api::Api::get_sprite_count(&api::Default); + unsafe { f() } +} + + +/// Sets the clipping rectangle for all sprites with a Z index within `start_z` and `end_z` __inclusive__. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::setClipRectsInRange`] +#[doc(alias = "sys::ffi::playdate_sprite::setClipRectsInRange")] +pub fn set_clip_rects_in_range(clip: LCDRect, start_z: c_int, end_z: c_int) { + let f = api::Api::set_clip_rects_in_range(&api::Default); + unsafe { f(clip, start_z, end_z) } +} + +/// Clears the clipping rectangle for all sprites with a Z index within `start_z` and `end_z` __inclusive__. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::clearClipRectsInRange`] +#[doc(alias = "sys::ffi::playdate_sprite::clearClipRectsInRange")] +pub fn clear_clip_rects_in_range(start_z: c_int, end_z: c_int) { + let f = api::Api::clear_clip_rects_in_range(&api::Default); + unsafe { f(start_z, end_z) } +} + + +/// Frees and reallocates internal collision data, resetting everything to its default state. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::resetCollisionWorld`] +#[doc(alias = "sys::ffi::playdate_sprite::resetCollisionWorld")] +pub fn reset_collision_world() { + let f = api::Api::reset_collision_world(&api::Default); + unsafe { f() } +} + + +/// Returns an slice of all sprites with collision rects containing the point at `x`, `y`. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::querySpritesAtPoint`] +#[doc(alias = "sys::ffi::playdate_sprite::querySpritesAtPoint")] +pub fn query_sprites_at_point(x: c_float, y: c_float) -> &'static [SpriteRef] { + let mut len: c_int = 0; + let api = api::Default; + let f = api.query_sprites_at_point(); + let ptr = unsafe { f(x, y, &mut len) }; + let slice = unsafe { core::slice::from_raw_parts(ptr, len as _) }; + unsafe { core::mem::transmute(slice) } +} + +/// Returns an slice of all sprites with collision rects +/// that intersect the `width` by `height` rect at `x`, `y`. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::querySpritesInRect`] +#[doc(alias = "sys::ffi::playdate_sprite::querySpritesInRect")] +pub fn query_sprites_in_rect(x: c_float, y: c_float, width: c_float, height: c_float) -> &'static [SpriteRef] { + let mut len: c_int = 0; + let f = api::Api::query_sprites_in_rect(&api::Default); + let ptr = unsafe { f(x, y, width, height, &mut len) }; + let slice = unsafe { core::slice::from_raw_parts(ptr, len as _) }; + unsafe { core::mem::transmute(slice) } +} + +/// Returns an slice of all sprites with collision rects +/// that intersect the line connecting `x1`, `y1` and `x2`, `y2`. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::querySpritesAlongLine`] +#[doc(alias = "sys::ffi::playdate_sprite::querySpritesAlongLine")] +pub fn query_sprites_along_line(x1: c_float, y1: c_float, x2: c_float, y2: c_float) -> &'static [SpriteRef] { + let mut len: c_int = 0; + let f = api::Api::query_sprites_along_line(&api::Default); + let ptr = unsafe { f(x1, y1, x2, y2, &mut len) }; + let slice = unsafe { core::slice::from_raw_parts(ptr, len as _) }; + unsafe { core::mem::transmute(slice) } +} + +/// Returns an slice of [`SpriteQueryInfo`]s for all sprites with collision rects +/// that intersect the line connecting `x1`, `y1` and `x2`, `y2`. +/// +/// If you don’t need this information, use [`query_sprites_along_line`] as it will be faster. +/// +/// Equivalent to [`sys::ffi::playdate_sprite::querySpriteInfoAlongLine`] +#[doc(alias = "sys::ffi::playdate_sprite::querySpriteInfoAlongLine")] +pub fn query_sprite_info_along_line(x1: c_float, + y1: c_float, + x2: c_float, + y2: c_float) + -> &'static [SpriteQueryInfo] { + let mut len: c_int = 0; + let f = api::Api::query_sprite_info_along_line(&api::Default); + let ptr = unsafe { f(x1, y1, x2, y2, &mut len) }; + unsafe { core::slice::from_raw_parts(ptr, len as _) } +} + + +/// Returns an slice of all sprites that have collide rects that are currently overlapping. +/// +/// Each consecutive pair of sprites is overlapping (eg. 0 & 1 overlap, 2 & 3 overlap, etc). +/// +/// Equivalent to [`sys::ffi::playdate_sprite::allOverlappingSprites`] +#[doc(alias = "sys::ffi::playdate_sprite::allOverlappingSprites")] +pub fn all_overlapping_sprites() -> &'static [SpriteRef] { + let f = api::Api::all_overlapping_sprites(&api::Default); + let mut len: c_int = 0; + let ptr = unsafe { f(&mut len) }; + let slice = unsafe { core::slice::from_raw_parts(ptr, len as _) }; + unsafe { core::mem::transmute(slice) } +} + + +pub trait AnySprite: AsRaw + SpriteApi {} +impl AnySprite for &'_ T {} + + +pub trait SpriteApi { + /// Type of inner API access-point. + type Api: api::Api; + + /// Get a copy of inner api access point. + fn api(&self) -> Self::Api + where Self::Api: Copy; + + /// Get a ref to inner api access point. + fn api_ref(&self) -> &Self::Api; +} + +impl SpriteApi for &'_ T { + type Api = T::Api; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + (*self).api() + } + + fn api_ref(&self) -> &Self::Api { (*self).api_ref() } +} + + +pub trait TypedSprite: AsRaw + SpriteApi { + /// Associated user-data with sprite. + type Userdata; + /// Should be freed when sprite is dropped. + const FREE_ON_DROP: bool; +} diff --git a/api/sprite/src/sprite.rs b/api/sprite/src/sprite.rs new file mode 100644 index 00000000..1872fa18 --- /dev/null +++ b/api/sprite/src/sprite.rs @@ -0,0 +1,691 @@ +//! Basic Sprite implementations. + +use core::ffi::c_int; +use core::ffi::c_void; +use core::ffi::c_float; +use core::marker::PhantomData; +use alloc::boxed::Box; + +use sys::traits::AsRaw; + +use sys::ffi::SpriteCollisionInfo; +use sys::ffi::LCDRect; +use sys::ffi::LCDSprite; +use sys::ffi::PDRect; + +use gfx::bitmap::AnyBitmap; +use gfx::bitmap::BitmapRef; +use gfx::bitmap::BitmapDrawMode; +use gfx::bitmap::BitmapFlip; + +use crate::AnySprite; +use crate::SpriteApi; +use crate::TypedSprite; +use crate::api; + + +pub type OwnedSprite = Sprite; +pub type SharedSprite = Sprite; + + +impl TypedSprite for Sprite { + type Userdata = UD; + const FREE_ON_DROP: bool = FOD; +} + + +impl AnySprite for SpriteRef {} +impl AnySprite for Sprite {} + + +impl SpriteApi for SpriteRef { + type Api = api::Default; + + fn api(&self) -> Self::Api + where Self::Api: Copy { + api::Default::default() + } + + fn api_ref(&self) -> &Self::Api { + static API: api::Default = api::Default; + &API + } +} + +impl SpriteApi for Sprite { + type Api = Api; + fn api(&self) -> Api + where Self::Api: Copy { + self.1 + } + + fn api_ref(&self) -> &Self::Api { &self.1 } +} + + +#[repr(transparent)] +#[derive(Copy, Clone, Debug)] +pub struct SpriteRef(*mut LCDSprite); + +impl From<*mut LCDSprite> for SpriteRef { + fn from(ptr: *mut LCDSprite) -> Self { Self(ptr) } +} + +impl AsRaw for SpriteRef { + type Type = LCDSprite; + unsafe fn as_raw(&self) -> *mut LCDSprite { self.0 } +} + +impl SpriteRef { + pub fn into_sprite(self) -> Sprite::Api, false> { + Sprite(unsafe { self.as_raw() }, self.api(), PhantomData) + } + + pub fn into_sprite_with(self, api: Api) -> Sprite { + Sprite(unsafe { self.as_raw() }, api, PhantomData) + } +} + + +#[derive(Debug)] +pub struct Sprite(*mut LCDSprite, Api, PhantomData); + +impl AsRaw for Sprite { + type Type = LCDSprite; + unsafe fn as_raw(&self) -> *mut LCDSprite { self.0 } +} + +impl AsRef for Sprite { + fn as_ref(&self) -> &Self { self } +} +impl AsMut for Sprite { + fn as_mut(&mut self) -> &mut Self { self } +} + +impl From for Sprite where Api: api::Api + Default { + fn from(sprite: SpriteRef) -> Self { Self(unsafe { sprite.as_raw() }, Api::default(), PhantomData) } +} + +impl Clone for Sprite { + fn clone(&self) -> Self { + let f = self.1.copy(); + let ptr = unsafe { f(self.0) }; + Self(ptr, self.1.clone(), PhantomData) + } +} + +impl Drop for Sprite { + fn drop(&mut self) { + if FOD && !self.0.is_null() { + if let Some(ud) = self.take_userdata() { + drop(ud); + let f = self.1.set_userdata(); + unsafe { f(self.0, core::ptr::null_mut()) } + } + + let f = self.1.free_sprite(); + unsafe { f(self.0) } + self.0 = core::ptr::null_mut(); + } + } +} + +impl Sprite { + /// Convert this sprite into the same sprite that will not be freed on drop. + /// That means that only C-part of the sprite will be freed. + /// + /// __Safety is guaranteed by the caller.__ + pub fn into_shared(mut self) -> Sprite { + let res = Sprite(self.0, self.1, self.2); + self.0 = core::ptr::null_mut(); + res + } +} + + +impl Sprite { + /// Allocates and returns a new Sprite with [`default`](api::Default) api access-point. + /// + /// To create a sprite with a custom api access-point, use [`with_api`](Sprite::with_api). + /// + /// See also [`sys::ffi::playdate_sprite::newSprite`] + #[doc(alias = "sys::ffi::playdate_sprite::newSprite")] + pub fn new() -> Self { + let api = Default::default(); + Self::new_with(api) + } +} + +impl Sprite { + /// Allocates and returns a new Sprite with given `api`. + /// + /// See also [`sys::ffi::playdate_sprite::newSprite`] + #[doc(alias = "sys::ffi::playdate_sprite::newSprite")] + pub fn new_with(api: Api) -> Self { + let f = api.new_sprite(); + let ptr = unsafe { f() }; + Self(ptr, api, PhantomData) + } +} + + +impl Sprite { + /// Adds the this sprite to the display list, so that it is drawn in the current scene. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::addSprite`] + #[doc(alias = "sys::ffi::playdate_sprite::addSprite")] + pub fn add(&self) { + let f = self.1.add_sprite(); + unsafe { f(self.0) } + } + + /// Removes the this sprite from the display list. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::removeSprite`] + #[doc(alias = "sys::ffi::playdate_sprite::removeSprite")] + pub fn remove(&self) { + let f = self.1.remove_sprite(); + unsafe { f(self.0) } + } + + + /// Sets the bounds of the sprite with bounds. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setBounds`] + #[doc(alias = "sys::ffi::playdate_sprite::setBounds")] + pub fn set_bounds(&self, bounds: PDRect) { + let f = self.1.set_bounds(); + unsafe { f(self.0, bounds) } + } + + /// Returns the bounds of the sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getBounds`] + #[doc(alias = "sys::ffi::playdate_sprite::getBounds")] + pub fn bounds(&self) -> PDRect { + let f = self.1.get_bounds(); + unsafe { f(self.0) } + } + + + /// Moves the sprite to `x`, `y` and resets its bounds based on the bitmap dimensions and center. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::moveTo`] + #[doc(alias = "sys::ffi::playdate_sprite::moveTo")] + pub fn move_to(&self, x: c_float, y: c_float) { + let f = self.1.move_to(); + unsafe { f(self.0, x, y) } + } + + /// Moves the sprite to by offsetting its current position by `dx`, `dy`. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::moveBy`] + #[doc(alias = "sys::ffi::playdate_sprite::moveBy")] + pub fn move_by(&self, dx: c_float, dy: c_float) { + let f = self.1.move_by(); + unsafe { f(self.0, dx, dy) } + } + + + /// Sets the sprite's image to the given bitmap. + /// + /// ⚠️ Caution: Using with draw function, call this method __before__ set callback. + /// Setting image __after__ setting draw callback is mostly crashes with SIGBUS. + /// + /// See also [`set_opaque`]. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setImage`] + #[doc(alias = "sys::ffi::playdate_sprite::setImage")] + pub fn set_image(&self, image: impl AnyBitmap, flip: BitmapFlip) { + let f = self.1.set_image(); + unsafe { f(self.0, image.as_raw(), flip) } + } + + /// Returns the bitmap currently assigned to the given sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getImage`] + #[doc(alias = "sys::ffi::playdate_sprite::getImage")] + pub fn image<'t>(&'t self) -> Option> { + let f = self.1.get_image(); + let ptr = unsafe { f(self.0) }; + if ptr.is_null() { + None + } else { + Some(BitmapRef::from(ptr)) + } + } + + + /// Sets the size. + /// The size is used to set the sprite’s bounds when calling [`move_to`]. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setSize`] + #[doc(alias = "sys::ffi::playdate_sprite::setSize")] + pub fn set_size(&self, width: c_float, height: c_float) { + let f = self.1.set_size(); + unsafe { f(self.0, width, height) } + } + + + /// Sets the Z order of the sprite. + /// Higher Z sprites are drawn on top of those with lower Z order. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setZIndex`] + #[doc(alias = "sys::ffi::playdate_sprite::setZIndex")] + pub fn set_z_index(&self, z_index: i16) { + let f = self.1.set_z_index(); + unsafe { f(self.0, z_index) } + } + + /// Returns the Z index of the sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getZIndex`] + #[doc(alias = "sys::ffi::playdate_sprite::getZIndex")] + pub fn z_index(&self) -> i16 { + let f = self.1.get_z_index(); + unsafe { f(self.0) } + } + + + /// Sets the mode for drawing the sprite’s bitmap. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setDrawMode`] + #[doc(alias = "sys::ffi::playdate_sprite::setDrawMode")] + pub fn set_draw_mode(&self, mode: BitmapDrawMode) { + let f = self.1.set_draw_mode(); + unsafe { f(self.0, mode) } + } + + + /// Flips the sprite's bitmap. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setImageFlip`] + #[doc(alias = "sys::ffi::playdate_sprite::setImageFlip")] + pub fn set_image_flip(&self, flip: BitmapFlip) { + let f = self.1.set_image_flip(); + unsafe { f(self.0, flip) } + } + + /// Returns the flip setting of the sprite’s bitmap. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getImageFlip`] + #[doc(alias = "sys::ffi::playdate_sprite::getImageFlip")] + pub fn image_flip(&self) -> BitmapFlip { + let f = self.1.get_image_flip(); + unsafe { f(self.0) } + } + + + /// Specifies a stencil image to be set on the frame buffer before the sprite is drawn. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setStencil`] + #[doc(alias = "sys::ffi::playdate_sprite::setStencil")] + pub fn set_stencil(&self, stencil: impl AnyBitmap) { + let f = self.1.set_stencil(); + unsafe { f(self.0, stencil.as_raw()) } + } + + + /// Sets the clipping rectangle for sprite drawing. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setClipRect`] + #[doc(alias = "sys::ffi::playdate_sprite::setClipRect")] + pub fn set_clip_rect(&self, clip: LCDRect) { + let f = self.1.set_clip_rect(); + unsafe { f(self.0, clip) } + } + + /// Clears the sprite’s clipping rectangle. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::clearClipRect`] + #[doc(alias = "sys::ffi::playdate_sprite::clearClipRect")] + pub fn clear_clip_rect(&self) { + let f = self.1.clear_clip_rect(); + unsafe { f(self.0) } + } + + + /// Set the `updates_enabled` flag of the sprite + /// (determines whether the sprite has its update function called). + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setUpdatesEnabled`] + #[doc(alias = "sys::ffi::playdate_sprite::setUpdatesEnabled")] + pub fn set_updates_enabled(&self, value: bool) { + let f = self.1.set_updates_enabled(); + unsafe { f(self.0, value.into()) } + } + + /// Get the `updates_enabled` flag of the sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::updatesEnabled`] + #[doc(alias = "sys::ffi::playdate_sprite::updatesEnabled")] + pub fn updates_enabled(&self) -> bool { + let f = self.1.updates_enabled(); + unsafe { f(self.0) == 1 } + } + + /// Set the collisions_enabled flag of the sprite + /// (along with the `collide_rect`, this determines whether the sprite participates in collisions). + /// + /// Set to `true` by default. + /// + /// See also [`set_collide_rect`], [`get_collide_rect`], [`clear_collide_rect`]. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setCollisionsEnabled`] + #[doc(alias = "sys::ffi::playdate_sprite::setCollisionsEnabled")] + pub fn set_collisions_enabled(&self, value: bool) { + let f = self.1.set_collisions_enabled(); + unsafe { f(self.0, value.into()) } + } + + /// Get the `collisions_enabled` flag of the sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::collisionsEnabled`] + #[doc(alias = "sys::ffi::playdate_sprite::collisionsEnabled")] + pub fn collisions_enabled(&self) -> bool { + let f = self.1.collisions_enabled(); + unsafe { f(self.0) == 1 } + } + + /// Set the visible flag of the given sprite + /// (determines whether the sprite has its draw function called). + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setVisible`] + #[doc(alias = "sys::ffi::playdate_sprite::setVisible")] + pub fn set_visible(&self, value: bool) { + let f = self.1.set_visible(); + unsafe { f(self.0, value.into()) } + } + + /// Get the visible flag of the sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::isVisible`] + #[doc(alias = "sys::ffi::playdate_sprite::isVisible")] + pub fn is_visible(&self) -> bool { + let f = self.1.is_visible(); + unsafe { f(self.0) == 1 } + } + + /// Marking a sprite opaque tells the sprite system that it doesn’t need to draw anything underneath the sprite, + /// since it will be overdrawn anyway. + /// + /// If you set an image without a mask/alpha channel on the sprite, it automatically sets the opaque flag. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setOpaque`] + #[doc(alias = "sys::ffi::playdate_sprite::setOpaque")] + pub fn set_opaque(&self, value: bool) { + let f = self.1.set_opaque(); + unsafe { f(self.0, value.into()) } + } + + /// Forces the sprite to redraw. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::markDirty`] + #[doc(alias = "sys::ffi::playdate_sprite::markDirty")] + pub fn mark_dirty(&self) { + let f = self.1.mark_dirty(); + unsafe { f(self.0) } + } + + /// Sets the tag of the sprite. + /// + /// This can be useful for identifying sprites or types of sprites when using the [collision][] API. + /// + /// [collision]: crate::callback::collision + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setTag`] + #[doc(alias = "sys::ffi::playdate_sprite::setTag")] + pub fn set_tag(&self, tag: u8) { + let f = self.1.set_tag(); + unsafe { f(self.0, tag) } + } + + /// Returns the tag of the given sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getTag`] + #[doc(alias = "sys::ffi::playdate_sprite::getTag")] + pub fn tag(&self) -> u8 { + let f = self.1.get_tag(); + unsafe { f(self.0) } + } + + /// When flag is set to `true`, + /// the sprite will draw in screen coordinates, + /// ignoring the currently-set `draw_offset`. + /// + /// This only affects drawing, + /// and should not be used on sprites being used for collisions, + /// which will still happen in world-space. + /// + /// See also [`playdate_graphics::set_draw_offset`]. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setIgnoresDrawOffset`] + #[doc(alias = "sys::ffi::playdate_sprite::setIgnoresDrawOffset")] + pub fn set_ignores_draw_offset(&self, value: bool) { + let f = self.1.set_ignores_draw_offset(); + unsafe { f(self.0, value.into()) } + } + + + /// Sets `x` and `y` to the current position of sprite. + /// + /// Equivalent to [`get_position_to`] and [`sys::ffi::playdate_sprite::getPosition`] + #[doc(alias = "sys::ffi::playdate_sprite::getPosition")] + pub fn position(&self) -> (c_float, c_float) { + let (mut x, mut y) = Default::default(); + self.position_to(&mut x, &mut y); + (x, y) + } + + /// Sets `x` and `y` to the current position of sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getPosition`] + #[doc(alias = "sys::ffi::playdate_sprite::getPosition")] + pub fn position_to(&self, x: &mut c_float, y: &mut c_float) { + let f = self.1.get_position(); + unsafe { f(self.0, x, y) } + } + + + // + // + // + // + // + + + // TODO: rename to more convenient names + + + /// Marks the area of the sprite, relative to its bounds, + /// to be checked for collisions with other sprites' collide rects. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setCollideRect`] + #[doc(alias = "sys::ffi::playdate_sprite::setCollideRect")] + pub fn set_collide_rect(&self, collide: PDRect) { + let f = self.1.set_collide_rect(); + unsafe { f(self.0, collide) } + } + + /// Returns the sprite’s collide rect. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getCollideRect`] + #[doc(alias = "sys::ffi::playdate_sprite::getCollideRect")] + pub fn collide_rect(&self) -> PDRect { + let f = self.1.get_collide_rect(); + unsafe { f(self.0) } + } + + /// Clears the sprite’s collide rect. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::clearCollideRect`] + #[doc(alias = "sys::ffi::playdate_sprite::clearCollideRect")] + pub fn clear_collide_rect(&self) { + let f = self.1.clear_collide_rect(); + unsafe { f(self.0) } + } + + /// Returns the same values as [`move_with_collisions`] but does not actually move the sprite. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::checkCollisions`] + #[doc(alias = "sys::ffi::playdate_sprite::check_collisions")] + #[must_use = "Result is borrowed by C-API"] + pub fn check_collisions(&self, + goal_x: c_float, + goal_y: c_float, + actual_x: &mut c_float, + actual_y: &mut c_float) + -> &[SpriteCollisionInfo] { + let f = self.1.check_collisions(); + let mut len: c_int = 0; + let ptr = unsafe { f(self.0, goal_x, goal_y, actual_x, actual_y, &mut len) }; + + if ptr.is_null() || len == 0 { + &[] + } else { + let slice = unsafe { core::slice::from_raw_parts(ptr, len as _) }; + slice + } + } + + /// Moves the sprite towards `goal_x`, `goal_y` taking collisions into account + /// and returns a slice of [`SpriteCollisionInfo`]. + /// + /// `actual_x`, `actual_y` are set to the sprite’s position after collisions. + /// If no collisions occurred, this will be the same as `goal_x`, `goal_y`. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::moveWithCollisions`] + #[doc(alias = "sys::ffi::playdate_sprite::moveWithCollisions")] + #[must_use = "Result is borrowed by C-API"] + pub fn move_with_collisions(&self, + goal_x: c_float, + goal_y: c_float, + actual_x: &mut c_float, + actual_y: &mut c_float) + -> &[SpriteCollisionInfo] { + let f = self.1.move_with_collisions(); + let mut len: c_int = 0; + let ptr = unsafe { f(self.0, goal_x, goal_y, actual_x, actual_y, &mut len) }; + + if ptr.is_null() || len == 0 { + &[] + } else { + let slice = unsafe { core::slice::from_raw_parts(ptr, len as _) }; + slice + } + } + + + /// Returns an slice of sprites that have collide rects + /// that are currently overlapping the given sprite’s collide rect. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::overlappingSprites`] + #[doc(alias = "sys::ffi::playdate_sprite::overlapping_sprites")] + #[must_use = "Result is borrowed by C-API"] + pub fn overlapping_sprites(&self) -> &[SpriteRef] { + let f = self.1.overlapping_sprites(); + let mut len: c_int = 0; + let ptr = unsafe { f(self.0, &mut len) }; + let slice = unsafe { core::slice::from_raw_parts(ptr, len as _) }; + unsafe { core::mem::transmute(slice) } + } + + + /// Sets the sprite’s stencil to the given pattern. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setStencilPattern`] + #[doc(alias = "sys::ffi::playdate_sprite::setStencilPattern")] + pub fn set_stencil_pattern(&self, pattern: &mut [u8; 8]) { + let f = self.1.set_stencil_pattern(); + unsafe { f(self.0, pattern) } + } + + /// Specifies a stencil image to be set on the frame buffer before the sprite is drawn. + /// + /// If tile is set, the stencil will be tiled. + /// + /// Tiled stencils must have __width__ evenly __divisible by 32__. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setStencilImage`] + #[doc(alias = "sys::ffi::playdate_sprite::setStencilImage")] + pub fn set_stencil_image(&self, stencil: impl AnyBitmap, tile: bool) { + let f = self.1.set_stencil_image(); + unsafe { f(self.0, stencil.as_raw(), tile.into()) } + } + + /// Clears the sprite’s stencil. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::clearStencil`] + #[doc(alias = "sys::ffi::playdate_sprite::clearStencil")] + pub fn clear_stencil(&self) { + let f = self.1.clear_stencil(); + unsafe { f(self.0) } + } + + + /// Sets custom data to the sprite. + /// + /// Used for associating the sprite with other data. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::setUserdata`] + #[doc(alias = "sys::ffi::playdate_sprite::setUserdata")] + pub fn set_userdata(&self, data: Userdata) { + let f = self.1.set_userdata(); + let userdata = Box::into_raw(Box::new(data)); + let ptr = userdata as *mut c_void; + unsafe { f(self.0, ptr) } + } + + /// Gets the _mutable__ reference to sprite’s userdata. + /// + /// Used for associating the sprite with other data. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getUserdata`] + #[doc(alias = "sys::ffi::playdate_sprite::get_userdata")] + pub fn userdata(&self) -> Option<&mut Userdata> { + let f = self.1.get_userdata(); + let ptr = unsafe { f(self.0) }; + if ptr.is_null() { + None + } else { + let ptr = ptr as *mut Userdata; + // TODO: check ptr is aligned to `UD` + unsafe { ptr.as_mut() } + } + } + + /// Returns __taken__ value the sprite’s userdata. + /// + /// Equivalent to [`sys::ffi::playdate_sprite::getUserdata`] + #[doc(alias = "sys::ffi::playdate_sprite::get_userdata")] + pub(crate) fn take_userdata(&self) -> Option> { + let f = self.1.get_userdata(); + let ptr = unsafe { f(self.0) }; + if ptr.is_null() { + None + } else { + // TODO: check ptr is aligned to `UD` + let ud = unsafe { Box::from_raw(ptr as *mut Userdata) }; + Some(ud) + } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + /// Ensure that SpriteRef have same size as LCDSprite. + fn sprite_ref_layout() { + assert_eq!( + core::mem::size_of::(), + core::mem::size_of::<*mut LCDSprite>() + ); + assert_eq!( + core::mem::size_of::<&[SpriteRef]>(), + core::mem::size_of::<&[*mut LCDSprite]>() + ); + } +} diff --git a/api/sys/Cargo.toml b/api/sys/Cargo.toml index 911ded25..7b137737 100644 --- a/api/sys/Cargo.toml +++ b/api/sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-sys" -version = "0.1.6" +version = "0.1.7" edition = "2021" build = "src/build.rs" diff --git a/api/sys/src/lib.rs b/api/sys/src/lib.rs index 7af972ae..358a69d3 100644 --- a/api/sys/src/lib.rs +++ b/api/sys/src/lib.rs @@ -23,6 +23,8 @@ pub extern crate alloc; pub mod log; +pub mod traits; + mod sys; pub use sys::*; diff --git a/api/sys/src/traits.rs b/api/sys/src/traits.rs new file mode 100644 index 00000000..4268fc9e --- /dev/null +++ b/api/sys/src/traits.rs @@ -0,0 +1,21 @@ +//! Helpful traits for API parts unification. + + +pub trait AsRaw { + type Type; + /// This method ia actually safe. + /// Unsafety is because so we're removing owners lifetime that used by some API parts. + unsafe fn as_raw(&self) -> *mut Self::Type; +} + +impl, Ptr> AsRaw for &'_ T { + type Type = Ptr; + #[inline(always)] + unsafe fn as_raw(&self) -> *mut Ptr { (*self).as_raw() } +} + +impl AsRaw for *mut T { + type Type = T; + #[inline(always)] + unsafe fn as_raw(&self) -> *mut T { *self } +} diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index 39eb4e1c..3b7ce030 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-playdate" -version = "0.2.3" +version = "0.2.4" edition = "2021" readme = "README.md" From ecdf8ae24d01d4388e577862843c597c74d8d6f9 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 13:36:55 +0400 Subject: [PATCH 2/9] fix tests trigger --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f38051be..3dc53dcf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,7 +2,7 @@ name: Tests on: pull_request: push: - branches: [main, master, "**"] + branches: [main, master] env: CARGO_INCREMENTAL: 1 From 47b59c335faf036febc19e0372fe585ca2c47728 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 13:39:17 +0400 Subject: [PATCH 3/9] update deps --- Cargo.lock | 85 +++++++++++++++++------------------------------------- 1 file changed, 27 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58bb25bf..f65f2eec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "0f2135563fb5c609d2b2b87c1e8ce7bc41b0b45430fa9661f457981503dd5bf0" dependencies = [ "memchr", ] @@ -469,9 +469,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.3" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" +checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" dependencies = [ "clap_builder", "clap_derive", @@ -479,9 +479,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.2" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" dependencies = [ "anstream", "anstyle", @@ -692,9 +692,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.65+curl-8.2.1" +version = "0.4.66+curl-8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" +checksum = "70c44a72e830f0e40ad90dda8a6ab6ed6314d39776599a58a2e5e37fbc6db5b9" dependencies = [ "cc", "libc", @@ -703,7 +703,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -899,9 +899,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" [[package]] name = "filetime" @@ -1455,7 +1455,7 @@ dependencies = [ "gix-command", "gix-config-value", "parking_lot", - "rustix 0.38.13", + "rustix", "thiserror", ] @@ -1740,9 +1740,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -1916,17 +1916,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "is-terminal" version = "0.4.9" @@ -1934,7 +1923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.13", + "rustix", "windows-sys 0.48.0", ] @@ -2105,12 +2094,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.7" @@ -2324,9 +2307,9 @@ dependencies = [ [[package]] name = "orion" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b11468cc6afd61a126fe3f91cc4cc8a0dbe7917d0a4b5e8357ba91cc47444462" +checksum = "7abdb10181903c8c4b016ba45d6d6d5af1a1e2a461aa4763a83b87f5df4695e5" dependencies = [ "fiat-crypto", "subtle", @@ -2929,20 +2912,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.13" @@ -2952,7 +2921,7 @@ dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.7", + "linux-raw-sys", "windows-sys 0.48.0", ] @@ -3306,7 +3275,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix 0.38.13", + "rustix", "windows-sys 0.48.0", ] @@ -3323,20 +3292,20 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.37.23", + "rustix", "windows-sys 0.48.0", ] @@ -3499,9 +3468,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -3675,7 +3644,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.13", + "rustix", ] [[package]] From 9a0637e551e3ffa04e8e726214cabe1a10737447 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 13:54:58 +0400 Subject: [PATCH 4/9] CI: disable sync-labels, fix labels for labeller action --- .github/labeler.yml | 6 ++---- .github/workflows/labeler.yml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index cab3a8b8..6cbfa9f7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,9 +1,7 @@ api: - any: - - "api/**/*.rs" - - "api/**/*.toml" - - "components/**/*.rs" - - "components/**/*.toml" + - "api/**" + - "components/**" pre-built bindings: - any: diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 29273899..21d9a60e 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -13,4 +13,4 @@ jobs: - uses: actions/labeler@v4 with: dot: false - sync-labels: true + sync-labels: false From 72b0af446363f3197fae918fdddecbf402dd1819 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 14:16:50 +0400 Subject: [PATCH 5/9] add cfg condition for execution tests --- cargo/tests/run/simple.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cargo/tests/run/simple.rs b/cargo/tests/run/simple.rs index dbd32947..e7e452fa 100644 --- a/cargo/tests/run/simple.rs +++ b/cargo/tests/run/simple.rs @@ -1,3 +1,5 @@ +#![allow(unexpected_cfgs)] + use std::ffi::OsStr; use std::ffi::OsString; use std::fs::File; @@ -95,6 +97,8 @@ fn test_value() -> String { #[test] +#[cfg_attr(not(exec_tests), + ignore = "execution tests not requested, set RUSTFLAGS='--cfg exec_tests' to enable.")] #[cfg_attr(not(target_os = "macos"), ignore = "Simulator as headless works on mac and maybe on windows.")] fn run_metadata_workspace_root_dev() -> Result<()> { @@ -108,6 +112,8 @@ fn run_metadata_workspace_root_dev() -> Result<()> { } #[test] +#[cfg_attr(not(exec_tests), + ignore = "execution tests not requested, set RUSTFLAGS='--cfg exec_tests' to enable.")] #[cfg_attr(not(target_os = "macos"), ignore = "Simulator as headless works on mac and maybe on windows.")] fn run_metadata_workspace_root_release() -> Result<()> { From 67f98b48ded2b25b7f9180898b05763d17360472 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 14:25:20 +0400 Subject: [PATCH 6/9] enable execution tests --- .github/workflows/tests.yml | 7 +++++++ cargo/tests/run/simple.rs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3dc53dcf..b391717b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -217,6 +217,13 @@ jobs: cargo test -p=cargo-playdate -- --nocapture rm -rf ./target/tmp + - name: Execution Test + env: + RUSTFLAGS: --cfg exec_tests + run: | + cargo test -p=cargo-playdate run -- --nocapture + rm -rf ./target/tmp + use-tool: name: Examples defaults: diff --git a/cargo/tests/run/simple.rs b/cargo/tests/run/simple.rs index e7e452fa..7c5a2c27 100644 --- a/cargo/tests/run/simple.rs +++ b/cargo/tests/run/simple.rs @@ -43,7 +43,7 @@ fn run(crate_name: &str, crate_path: &Path, args: impl IntoIterator Date: Wed, 20 Sep 2023 15:02:41 +0400 Subject: [PATCH 7/9] CI: improvements --- .github/workflows/tests.yml | 104 ++++++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b391717b..a4b0a53d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,7 +62,7 @@ jobs: - name: Cache LLVM id: cache-llvm - if: matrix.os == 'windows-latest' + if: runner.os == 'Windows' uses: actions/cache@v3 with: path: ${{ runner.temp }}/llvm @@ -72,7 +72,7 @@ jobs: # https://github.com/rust-lang/rust-bindgen/issues/1797 # https://rust-lang.github.io/rust-bindgen/requirements.html#windows - name: Install LLVM - if: matrix.os == 'windows-latest' + if: runner.os == 'Windows' uses: KyleMayes/install-llvm-action@v1.8.3 with: version: "14.0" @@ -126,8 +126,8 @@ jobs: run: | cargo doc -p=playdate-sys -v --target=thumbv7em-none-eabihf --features=bindings-documentation,bindings-derive-default,bindings-derive-eq,bindings-derive-copy,bindings-derive-debug,bindings-derive-hash,bindings-derive-ord,bindings-derive-partialeq,bindings-derive-partialord - tools: - name: Tools + utils: + name: Utils defaults: run: shell: bash @@ -161,7 +161,7 @@ jobs: - name: Cache LLVM id: cache-llvm - if: matrix.os == 'windows-latest' + if: runner.os == 'Windows' uses: actions/cache@v3 with: path: ${{ runner.temp }}/llvm @@ -171,7 +171,7 @@ jobs: # https://github.com/rust-lang/rust-bindgen/issues/1797 # https://rust-lang.github.io/rust-bindgen/requirements.html#windows - name: Install LLVM - if: matrix.os == 'windows-latest' + if: runner.os == 'Windows' uses: KyleMayes/install-llvm-action@v1.8.3 with: version: "14.0" @@ -180,7 +180,7 @@ jobs: env: true - name: Install Deps - if: matrix.os == 'windows-latest' + if: runner.os == 'Windows' run: | # mingw-w64-x86_64-libusb choco install pkgconfiglite @@ -196,18 +196,88 @@ jobs: - name: SDK ${{ steps.sdk.outputs.version }} installed run: which pdc && pdc --version - - name: Check - run: cargo check --tests -p=cargo-playdate --all-features - - - name: Test Utils + - name: Test run: | cargo test -p=playdate-build-utils --all-features cargo test -p=playdate-build --all-features cargo test -p=playdate-tool --all-features + tool: + name: Tool + defaults: + run: + shell: bash + needs: format + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - macos-latest + - ubuntu-latest + # - windows-latest + sdk: + - latest + + steps: + - uses: actions/checkout@v4 + + - name: Cache + uses: actions/cache@v3 + with: + path: | + target/ + ~/.cargo + key: ${{ runner.os }}-cargo-tests-${{ hashFiles('Cargo.lock') }} + + - name: Config + run: | + mkdir -p .cargo + cp -rf .github/config.toml .cargo/config.toml + + - name: Cache LLVM + id: cache-llvm + if: runner.os == 'Windows' + uses: actions/cache@v3 + with: + path: ${{ runner.temp }}/llvm + key: llvm-14.0 + + # See: + # https://github.com/rust-lang/rust-bindgen/issues/1797 + # https://rust-lang.github.io/rust-bindgen/requirements.html#windows + - name: Install LLVM + if: runner.os == 'Windows' + uses: KyleMayes/install-llvm-action@v1.8.3 + with: + version: "14.0" + directory: ${{ runner.temp }}/llvm + cached: ${{ steps.cache-llvm.outputs.cache-hit }} + env: true + + - name: Install Deps + if: runner.os == 'Windows' + run: | + # mingw-w64-x86_64-libusb + choco install pkgconfiglite + pkg-config --cflags --libs libusb-1.0 + pkg-config --cflags --libs libusb + + - name: Install Playdate SDK ${{ matrix.sdk }} + id: sdk + uses: pd-rs/get-playdate-sdk@main + with: + version: ${{ matrix.sdk }} + + - name: SDK ${{ steps.sdk.outputs.version }} installed + run: which pdc && pdc --version + + - name: Check + run: cargo check --tests -p=cargo-playdate --all-features + # Simulator doesn't works in headless mode # - name: Install Sim Deps - # if: ${{ matrix.os == 'ubuntu-latest' }} + # if: ${{ runner.os == 'Linux' }} # run: | # sudo apt update # sudo apt -y install libwebkit2gtk-4.0-dev @@ -217,11 +287,13 @@ jobs: cargo test -p=cargo-playdate -- --nocapture rm -rf ./target/tmp - - name: Execution Test + - name: Execution + if: runner.os == 'macOS' + continue-on-error: true # this is flickering on CI 🤷🏻‍♂️ env: RUSTFLAGS: --cfg exec_tests run: | - cargo test -p=cargo-playdate run -- --nocapture + cargo test -p=cargo-playdate run -- --nocapture --test-threads=1 rm -rf ./target/tmp use-tool: @@ -264,7 +336,7 @@ jobs: - name: Cache LLVM id: cache-llvm - if: matrix.os == 'windows-latest' + if: runner.os == 'Windows' uses: actions/cache@v3 with: path: ${{ runner.temp }}/llvm @@ -274,7 +346,7 @@ jobs: # https://github.com/rust-lang/rust-bindgen/issues/1797 # https://rust-lang.github.io/rust-bindgen/requirements.html#windows - name: Install LLVM - if: matrix.os == 'windows-latest' + if: runner.os == 'Windows' uses: KyleMayes/install-llvm-action@v1.8.3 with: version: "14.0" From 019272ea0702f92aaf21f91ed702952f2f948639 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 15:04:56 +0400 Subject: [PATCH 8/9] CI: enable utils tests on windows --- .github/workflows/tests.yml | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a4b0a53d..3939c168 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -139,7 +139,7 @@ jobs: os: - macos-latest - ubuntu-latest - # - windows-latest + - windows-latest sdk: - latest @@ -159,26 +159,6 @@ jobs: mkdir -p .cargo cp -rf .github/config.toml .cargo/config.toml - - name: Cache LLVM - id: cache-llvm - if: runner.os == 'Windows' - uses: actions/cache@v3 - with: - path: ${{ runner.temp }}/llvm - key: llvm-14.0 - - # See: - # https://github.com/rust-lang/rust-bindgen/issues/1797 - # https://rust-lang.github.io/rust-bindgen/requirements.html#windows - - name: Install LLVM - if: runner.os == 'Windows' - uses: KyleMayes/install-llvm-action@v1.8.3 - with: - version: "14.0" - directory: ${{ runner.temp }}/llvm - cached: ${{ steps.cache-llvm.outputs.cache-hit }} - env: true - - name: Install Deps if: runner.os == 'Windows' run: | From 5dfd45a8096e7bc4f0d406f87cfb22b199b16236 Mon Sep 17 00:00:00 2001 From: Alexander Koz Date: Wed, 20 Sep 2023 15:17:00 +0400 Subject: [PATCH 9/9] CI: disable utils tests on windows --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3939c168..4fad8743 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -139,7 +139,7 @@ jobs: os: - macos-latest - ubuntu-latest - - windows-latest + # - windows-latest sdk: - latest