Merge remote-tracking branch 'upstream/master'
5
.github/workflows/build-and-test.yml
vendored
|
@ -18,6 +18,11 @@ jobs:
|
|||
steps:
|
||||
- name: Install build tools
|
||||
run: sudo apt-get update && sudo apt-get install build-essential binutils-arm-none-eabi libelf-dev zip -y
|
||||
- name: Install Miri
|
||||
run: |
|
||||
rustup toolchain install nightly --component miri
|
||||
rustup override set nightly
|
||||
cargo miri setup
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
|
|
6
.vscode/agb.code-workspace
vendored
|
@ -38,6 +38,12 @@
|
|||
},
|
||||
{
|
||||
"path": "../tools"
|
||||
},
|
||||
{
|
||||
"path": "../examples/combo"
|
||||
},
|
||||
{
|
||||
"path": "../agb-hashmap"
|
||||
}
|
||||
]
|
||||
}
|
18
CHANGELOG.md
|
@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- New `include_palette` macro for including every colour in an image as a `u16` slice.
|
||||
|
||||
### Changed
|
||||
- Changed the default template game.
|
||||
- `DynamicSprite` has a new API which changes the constructor and adds a `set_pixel` method.
|
||||
|
||||
## [0.15.0] - 2023/04/25
|
||||
|
||||
### Added
|
||||
- You can now import aseprite files directly (in addition to the already supported png and bmp files) when importing background tiles.
|
||||
- New additional unmanaged object API for interacting with a more straightforward manner with the underlying hardware.
|
||||
|
||||
### Changed
|
||||
- Importing background tiles has been improved. You no longer need to use `include_gfx!` with the toml file. Instead, use `include_background_gfx`. See the documentation for usage.
|
||||
- The hashmap implementation is now it its own crate, `agb-hashmap`. There is no change in API, but you can now use this for interop between non-agb code and agb code
|
||||
- Moved the existing object API to be the OamManaged API. The old names persist with deprecated notices on them.
|
||||
|
||||
## [0.14.0] - 2023/04/11
|
||||
|
||||
### Added
|
||||
|
|
|
@ -66,6 +66,8 @@ to just write games for the Game Boy Advance using this library:
|
|||
* Install with `cargo install just`
|
||||
* [mdbook](https://rust-lang.github.io/mdBook/index.html)
|
||||
* Install with `cargo install mdbook`
|
||||
* [miri](https://github.com/rust-lang/miri)
|
||||
* Some of the unsafe code is tested using miri, install with `rustup component add miri`
|
||||
|
||||
With all of this installed, you should be able to run a full build of agb using by running
|
||||
```sh
|
||||
|
@ -85,6 +87,8 @@ for performant decimals.
|
|||
|
||||
`agb-sound-converter` - a crate which converts wav files into a format supported by the game boy advance
|
||||
|
||||
`agb-hashmap` - an no_std hashmap implementation tuned for use on the game boy advance
|
||||
|
||||
`agb` - the main library code
|
||||
|
||||
`agb/examples` - basic examples often targeting 1 feature, you can run these using `just run-example <example-name>`
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
[package]
|
||||
name = "agb_fixnum"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
description = "Library for abstracting over fixed precision numbers. Designed for use with the agb library for the Game Boy Advance"
|
||||
repository = "https://github.com/agbrs/agb"
|
||||
|
||||
[dependencies]
|
||||
agb_macros = { version = "0.14.0", path = "../agb-macros" }
|
||||
agb_macros = { version = "0.15.0", path = "../agb-macros" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
162
agb-gbafix/Cargo.lock
generated
|
@ -4,7 +4,7 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "agb-gbafix"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
|
@ -15,42 +15,51 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.2.6"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f"
|
||||
checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"concolor-override",
|
||||
"concolor-query",
|
||||
"colorchoice",
|
||||
"is-terminal",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "0.3.5"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
|
||||
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116"
|
||||
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "0.2.0"
|
||||
name = "anstyle-query"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -79,18 +88,18 @@ checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.2.1"
|
||||
version = "4.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3"
|
||||
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.2.1"
|
||||
version = "4.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f"
|
||||
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -106,19 +115,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
|
||||
|
||||
[[package]]
|
||||
name = "concolor-override"
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f"
|
||||
|
||||
[[package]]
|
||||
name = "concolor-query"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
|
||||
dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "elf"
|
||||
|
@ -128,13 +128,13 @@ checksum = "e2b183d6ce6ca4cf30e3db37abf5b52568b5f9015c97d9fbdd7026aa5dcdd758"
|
|||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
|
||||
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -170,7 +170,7 @@ checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -182,33 +182,33 @@ dependencies = [
|
|||
"hermit-abi",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.141"
|
||||
version = "0.2.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
||||
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.1"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
|
||||
checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.8"
|
||||
version = "0.37.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635"
|
||||
checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -223,37 +223,13 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -262,93 +238,51 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.0",
|
||||
"windows_aarch64_msvc 0.48.0",
|
||||
"windows_i686_gnu 0.48.0",
|
||||
"windows_i686_msvc 0.48.0",
|
||||
"windows_x86_64_gnu 0.48.0",
|
||||
"windows_x86_64_gnullvm 0.48.0",
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "agb-gbafix"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
authors = ["Gwilym Inzani <email@gwilym.dev>"]
|
||||
license = "GPL-3.0"
|
||||
|
@ -13,3 +13,13 @@ gbafix = "1"
|
|||
bytemuck = "1"
|
||||
anyhow = "1"
|
||||
clap = "4"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
24
agb-hashmap/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "agb_hashmap"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
description = "A simple no_std hashmap implementation intended for use in the `agb` library"
|
||||
repository = "https://github.com/agbrs/agb"
|
||||
|
||||
[dependencies]
|
||||
rustc-hash = { version = "1", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = { version = "0.8", default-features = false, features = ["small_rng"] }
|
||||
lazy_static = "1.4"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
318
agb-hashmap/benches/bench.rs
Normal file
|
@ -0,0 +1,318 @@
|
|||
// These benchmarks were taken from hashbrown. They are impossible to run
|
||||
// on the target GBA hardware, but hopefully running these on something like a
|
||||
// raspberry pi zero will give something comparable.
|
||||
|
||||
// This benchmark suite contains some benchmarks along a set of dimensions:
|
||||
// Int key distribution: low bit heavy, top bit heavy, and random.
|
||||
// Task: basic functionality: insert, insert_erase, lookup, lookup_fail, iter
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use test::{black_box, Bencher};
|
||||
|
||||
use agb_hashmap::HashMap;
|
||||
use std::sync::atomic::{self, AtomicUsize};
|
||||
|
||||
const SIZE: usize = 1000;
|
||||
|
||||
type StdHashMap<K, V> = std::collections::hash_map::HashMap<K, V>;
|
||||
|
||||
// A random key iterator.
|
||||
#[derive(Clone, Copy)]
|
||||
struct RandomKeys {
|
||||
state: usize,
|
||||
}
|
||||
|
||||
impl RandomKeys {
|
||||
fn new() -> Self {
|
||||
RandomKeys { state: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RandomKeys {
|
||||
type Item = usize;
|
||||
fn next(&mut self) -> Option<usize> {
|
||||
// Add 1 then multiply by some 32 bit prime.
|
||||
self.state = self.state.wrapping_add(1).wrapping_mul(3_787_392_781);
|
||||
Some(self.state)
|
||||
}
|
||||
}
|
||||
|
||||
// Just an arbitrary side effect to make the maps not shortcircuit to the non-dropping path
|
||||
// when dropping maps/entries (most real world usages likely have drop in the key or value)
|
||||
lazy_static::lazy_static! {
|
||||
static ref SIDE_EFFECT: AtomicUsize = AtomicUsize::new(0);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DropType(usize);
|
||||
impl Drop for DropType {
|
||||
fn drop(&mut self) {
|
||||
SIDE_EFFECT.fetch_add(self.0, atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bench_suite {
|
||||
($bench_macro:ident, $bench_agb_hashmap_serial:ident, $bench_std_serial:ident,
|
||||
$bench_agb_hashmap_highbits:ident, $bench_std_highbits:ident,
|
||||
$bench_agb_hashmap_random:ident, $bench_std_random:ident) => {
|
||||
$bench_macro!($bench_agb_hashmap_serial, HashMap, 0..);
|
||||
$bench_macro!($bench_std_serial, StdHashMap, 0..);
|
||||
$bench_macro!(
|
||||
$bench_agb_hashmap_highbits,
|
||||
HashMap,
|
||||
(0..).map(usize::swap_bytes)
|
||||
);
|
||||
$bench_macro!(
|
||||
$bench_std_highbits,
|
||||
StdHashMap,
|
||||
(0..).map(usize::swap_bytes)
|
||||
);
|
||||
$bench_macro!($bench_agb_hashmap_random, HashMap, RandomKeys::new());
|
||||
$bench_macro!($bench_std_random, StdHashMap, RandomKeys::new());
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bench_insert {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::with_capacity(SIZE);
|
||||
b.iter(|| {
|
||||
m.clear();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
m.insert(i, (DropType(i), [i; 20]));
|
||||
}
|
||||
black_box(&mut m);
|
||||
});
|
||||
eprintln!("{}", SIDE_EFFECT.load(atomic::Ordering::SeqCst));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_insert,
|
||||
agb_hashmap_insert_serial,
|
||||
std_hashmap_insert_serial,
|
||||
agb_hashmap_insert_highbits,
|
||||
std_hashmap_insert_highbits,
|
||||
agb_hashmap_insert_random,
|
||||
std_hashmap_insert_random
|
||||
);
|
||||
|
||||
macro_rules! bench_grow_insert {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let mut m = $maptype::default();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
black_box(&mut m);
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_grow_insert,
|
||||
agb_hashmap_grow_insert_serial,
|
||||
std_hashmap_grow_insert_serial,
|
||||
agb_hashmap_grow_insert_highbits,
|
||||
std_hashmap_grow_insert_highbits,
|
||||
agb_hashmap_grow_insert_random,
|
||||
std_hashmap_grow_insert_random
|
||||
);
|
||||
|
||||
macro_rules! bench_insert_erase {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut base = $maptype::default();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
base.insert(i, DropType(i));
|
||||
}
|
||||
let skip = $keydist.skip(SIZE);
|
||||
b.iter(|| {
|
||||
let mut m = base.clone();
|
||||
let mut add_iter = skip.clone();
|
||||
let mut remove_iter = $keydist;
|
||||
// While keeping the size constant,
|
||||
// replace the first keydist with the second.
|
||||
for (add, remove) in (&mut add_iter).zip(&mut remove_iter).take(SIZE) {
|
||||
m.insert(add, DropType(add));
|
||||
black_box(m.remove(&remove));
|
||||
}
|
||||
black_box(m);
|
||||
});
|
||||
eprintln!("{}", SIDE_EFFECT.load(atomic::Ordering::SeqCst));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_insert_erase,
|
||||
agb_hashmap_insert_erase_serial,
|
||||
std_hashmap_insert_erase_serial,
|
||||
agb_hashmap_insert_erase_highbits,
|
||||
std_hashmap_insert_erase_highbits,
|
||||
agb_hashmap_insert_erase_random,
|
||||
std_hashmap_insert_erase_random
|
||||
);
|
||||
|
||||
macro_rules! bench_lookup {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::default();
|
||||
for i in $keydist.take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for i in $keydist.take(SIZE) {
|
||||
black_box(m.get(&i));
|
||||
}
|
||||
});
|
||||
eprintln!("{}", SIDE_EFFECT.load(atomic::Ordering::SeqCst));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_lookup,
|
||||
agb_hashmap_lookup_serial,
|
||||
std_hashmap_lookup_serial,
|
||||
agb_hashmap_lookup_highbits,
|
||||
std_hashmap_lookup_highbits,
|
||||
agb_hashmap_lookup_random,
|
||||
std_hashmap_lookup_random
|
||||
);
|
||||
|
||||
macro_rules! bench_lookup_fail {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::default();
|
||||
let mut iter = $keydist;
|
||||
for i in (&mut iter).take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for i in (&mut iter).take(SIZE) {
|
||||
black_box(m.get(&i));
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_lookup_fail,
|
||||
agb_hashmap_lookup_fail_serial,
|
||||
std_hashmap_lookup_fail_serial,
|
||||
agb_hashmap_lookup_fail_highbits,
|
||||
std_hashmap_lookup_fail_highbits,
|
||||
agb_hashmap_lookup_fail_random,
|
||||
std_hashmap_lookup_fail_random
|
||||
);
|
||||
|
||||
macro_rules! bench_iter {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::default();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for i in &m {
|
||||
black_box(i);
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_iter,
|
||||
agb_hashmap_iter_serial,
|
||||
std_hashmap_iter_serial,
|
||||
agb_hashmap_iter_highbits,
|
||||
std_hashmap_iter_highbits,
|
||||
agb_hashmap_iter_random,
|
||||
std_hashmap_iter_random
|
||||
);
|
||||
|
||||
macro_rules! clone_bench {
|
||||
($maptype:ident) => {
|
||||
use super::DropType;
|
||||
use test::{black_box, Bencher};
|
||||
|
||||
#[bench]
|
||||
fn clone_small(b: &mut Bencher) {
|
||||
let mut m = $maptype::new();
|
||||
for i in 0..10 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
black_box(m.clone());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn clone_from_small(b: &mut Bencher) {
|
||||
let mut m = $maptype::new();
|
||||
let mut m2 = $maptype::new();
|
||||
for i in 0..10 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
m2.clone_from(&m);
|
||||
black_box(&mut m2);
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn clone_large(b: &mut Bencher) {
|
||||
let mut m = $maptype::new();
|
||||
for i in 0..1000 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
black_box(m.clone());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn clone_from_large(b: &mut Bencher) {
|
||||
let mut m = $maptype::new();
|
||||
let mut m2 = $maptype::new();
|
||||
for i in 0..1000 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
m2.clone_from(&m);
|
||||
black_box(&mut m2);
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod agb_hashmap_clone_benches {
|
||||
use agb_hashmap::HashMap;
|
||||
clone_bench!(HashMap);
|
||||
}
|
||||
|
||||
mod std_hashmap_clone_benches {
|
||||
use std::collections::hash_map::HashMap;
|
||||
clone_bench!(HashMap);
|
||||
}
|
3
agb-hashmap/rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["miri"]
|
170
agb-hashmap/src/node.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
use core::{
|
||||
mem::{self, MaybeUninit},
|
||||
ptr,
|
||||
};
|
||||
|
||||
use crate::HashType;
|
||||
|
||||
pub(crate) struct Node<K, V> {
|
||||
hash: HashType,
|
||||
|
||||
// distance_to_initial_bucket = -1 => key and value are uninit.
|
||||
// distance_to_initial_bucket >= 0 => key and value are init
|
||||
distance_to_initial_bucket: i32,
|
||||
key: MaybeUninit<K>,
|
||||
value: MaybeUninit<V>,
|
||||
}
|
||||
|
||||
impl<K, V> Node<K, V> {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {
|
||||
hash: HashType::new(),
|
||||
distance_to_initial_bucket: -1,
|
||||
key: MaybeUninit::uninit(),
|
||||
value: MaybeUninit::uninit(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_with(key: K, value: V, hash: HashType) -> Self {
|
||||
Self {
|
||||
hash,
|
||||
distance_to_initial_bucket: 0,
|
||||
key: MaybeUninit::new(key),
|
||||
value: MaybeUninit::new(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn value_ref_unchecked(&self) -> &V {
|
||||
self.value.assume_init_ref()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn value_mut_unchecked(&mut self) -> &mut V {
|
||||
self.value.assume_init_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn key_ref(&self) -> Option<&K> {
|
||||
if self.distance_to_initial_bucket >= 0 {
|
||||
Some(
|
||||
// SAFETY: has a value
|
||||
unsafe { self.key.assume_init_ref() },
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn key_value_ref(&self) -> Option<(&K, &V)> {
|
||||
if self.has_value() {
|
||||
Some(
|
||||
// SAFETY: has a value
|
||||
unsafe { self.key_value_ref_unchecked() },
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn key_value_ref_unchecked(&self) -> (&K, &V) {
|
||||
(self.key.assume_init_ref(), self.value.assume_init_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn key_value_mut(&mut self) -> Option<(&K, &mut V)> {
|
||||
if self.has_value() {
|
||||
Some(
|
||||
// SAFETY: has a value
|
||||
unsafe { (self.key.assume_init_ref(), self.value.assume_init_mut()) },
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_value(&self) -> bool {
|
||||
self.distance_to_initial_bucket >= 0
|
||||
}
|
||||
|
||||
pub(crate) fn take_key_value(&mut self) -> Option<(K, V, HashType)> {
|
||||
if self.has_value() {
|
||||
let key = mem::replace(&mut self.key, MaybeUninit::uninit());
|
||||
let value = mem::replace(&mut self.value, MaybeUninit::uninit());
|
||||
self.distance_to_initial_bucket = -1;
|
||||
|
||||
Some(
|
||||
// SAFETY: has a value
|
||||
unsafe { (key.assume_init(), value.assume_init(), self.hash) },
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn replace_value_unchecked(&mut self, value: V) -> V {
|
||||
let old_value = mem::replace(&mut self.value, MaybeUninit::new(value));
|
||||
old_value.assume_init()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn replace_unchecked(&mut self, key: K, value: V) -> (K, V) {
|
||||
let old_key = mem::replace(&mut self.key, MaybeUninit::new(key));
|
||||
let old_value = mem::replace(&mut self.value, MaybeUninit::new(value));
|
||||
|
||||
(old_key.assume_init(), old_value.assume_init())
|
||||
}
|
||||
|
||||
pub(crate) fn increment_distance(&mut self) {
|
||||
self.distance_to_initial_bucket += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn decrement_distance(&mut self) {
|
||||
self.distance_to_initial_bucket -= 1;
|
||||
|
||||
assert!(
|
||||
self.distance_to_initial_bucket >= 0,
|
||||
"Cannot decrement distance below 0"
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn distance(&self) -> i32 {
|
||||
self.distance_to_initial_bucket
|
||||
}
|
||||
|
||||
pub(crate) fn hash(&self) -> HashType {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Drop for Node<K, V> {
|
||||
fn drop(&mut self) {
|
||||
if self.has_value() {
|
||||
// SAFETY: has a value
|
||||
unsafe {
|
||||
ptr::drop_in_place(self.key.as_mut_ptr());
|
||||
ptr::drop_in_place(self.value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Clone for Node<K, V>
|
||||
where
|
||||
K: Clone,
|
||||
V: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
if let Some((k, v)) = self.key_value_ref() {
|
||||
Self {
|
||||
hash: self.hash,
|
||||
distance_to_initial_bucket: self.distance_to_initial_bucket,
|
||||
key: MaybeUninit::new(k.clone()),
|
||||
value: MaybeUninit::new(v.clone()),
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
hash: self.hash,
|
||||
|
||||
distance_to_initial_bucket: self.distance_to_initial_bucket,
|
||||
key: MaybeUninit::uninit(),
|
||||
value: MaybeUninit::uninit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
227
agb-hashmap/src/node_storage.rs
Normal file
|
@ -0,0 +1,227 @@
|
|||
use core::{alloc::Allocator, borrow::Borrow, mem};
|
||||
|
||||
use alloc::{alloc::Global, vec::Vec};
|
||||
|
||||
use crate::{node::Node, number_before_resize, ClonableAllocator, HashType};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct NodeStorage<K, V, ALLOCATOR: Allocator = Global> {
|
||||
nodes: Vec<Node<K, V>, ALLOCATOR>,
|
||||
max_distance_to_initial_bucket: i32,
|
||||
|
||||
number_of_items: usize,
|
||||
max_number_before_resize: usize,
|
||||
}
|
||||
|
||||
impl<K, V, ALLOCATOR: ClonableAllocator> NodeStorage<K, V, ALLOCATOR> {
|
||||
pub(crate) fn with_size_in(capacity: usize, alloc: ALLOCATOR) -> Self {
|
||||
assert!(capacity.is_power_of_two(), "Capacity must be a power of 2");
|
||||
|
||||
let mut nodes = Vec::with_capacity_in(capacity, alloc);
|
||||
for _ in 0..capacity {
|
||||
nodes.push(Node::new());
|
||||
}
|
||||
|
||||
Self {
|
||||
nodes,
|
||||
max_distance_to_initial_bucket: 0,
|
||||
number_of_items: 0,
|
||||
max_number_before_resize: number_before_resize(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn allocator(&self) -> &ALLOCATOR {
|
||||
self.nodes.allocator()
|
||||
}
|
||||
|
||||
pub(crate) fn capacity(&self) -> usize {
|
||||
self.max_number_before_resize
|
||||
}
|
||||
|
||||
pub(crate) fn backing_vec_size(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.number_of_items
|
||||
}
|
||||
|
||||
pub(crate) fn insert_new(&mut self, key: K, value: V, hash: HashType) -> usize {
|
||||
debug_assert!(
|
||||
self.capacity() > self.len(),
|
||||
"Do not have space to insert into len {} with {}",
|
||||
self.backing_vec_size(),
|
||||
self.len()
|
||||
);
|
||||
|
||||
let mut new_node = Node::new_with(key, value, hash);
|
||||
let mut inserted_location = usize::MAX;
|
||||
|
||||
loop {
|
||||
let location =
|
||||
(new_node.hash() + new_node.distance()).fast_mod(self.backing_vec_size());
|
||||
|
||||
let current_node = &mut self.nodes[location];
|
||||
|
||||
if current_node.has_value() {
|
||||
if current_node.distance() <= new_node.distance() {
|
||||
mem::swap(&mut new_node, current_node);
|
||||
|
||||
if inserted_location == usize::MAX {
|
||||
inserted_location = location;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.nodes[location] = new_node;
|
||||
if inserted_location == usize::MAX {
|
||||
inserted_location = location;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
new_node.increment_distance();
|
||||
self.max_distance_to_initial_bucket =
|
||||
new_node.distance().max(self.max_distance_to_initial_bucket);
|
||||
}
|
||||
|
||||
self.number_of_items += 1;
|
||||
inserted_location
|
||||
}
|
||||
|
||||
pub(crate) fn retain<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(&K, &mut V) -> bool,
|
||||
{
|
||||
let num_nodes = self.nodes.len();
|
||||
let mut i = 0;
|
||||
|
||||
while i < num_nodes {
|
||||
let node = &mut self.nodes[i];
|
||||
|
||||
if let Some((k, v)) = node.key_value_mut() {
|
||||
if !f(k, v) {
|
||||
self.remove_from_location(i);
|
||||
|
||||
// Need to continue before adding 1 to i because remove from location could
|
||||
// put the element which was next into the ith location in the nodes array,
|
||||
// so we need to check if that one needs removing too.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn remove_from_location(&mut self, location: usize) -> V {
|
||||
let mut current_location = location;
|
||||
self.number_of_items -= 1;
|
||||
|
||||
loop {
|
||||
let next_location =
|
||||
HashType::from(current_location + 1).fast_mod(self.backing_vec_size());
|
||||
|
||||
// if the next node is empty, or the next location has 0 distance to initial bucket then
|
||||
// we can clear the current node
|
||||
if !self.nodes[next_location].has_value() || self.nodes[next_location].distance() == 0 {
|
||||
return self.nodes[current_location].take_key_value().unwrap().1;
|
||||
}
|
||||
|
||||
self.nodes.swap(current_location, next_location);
|
||||
self.nodes[current_location].decrement_distance();
|
||||
current_location = next_location;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn location<Q>(&self, key: &Q, hash: HashType) -> Option<usize>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Eq + ?Sized,
|
||||
{
|
||||
for distance_to_initial_bucket in 0..(self.max_distance_to_initial_bucket + 1) {
|
||||
let location = (hash + distance_to_initial_bucket).fast_mod(self.backing_vec_size());
|
||||
|
||||
let node = &self.nodes[location];
|
||||
|
||||
// if we've seen a node which is further from home than what we'd expect to find, then
|
||||
// our node cannot exist because it would've been inserted here.
|
||||
if node.distance() < distance_to_initial_bucket {
|
||||
return None;
|
||||
}
|
||||
|
||||
let node_key_ref = node.key_ref()?;
|
||||
|
||||
if node_key_ref.borrow() == key {
|
||||
return Some(location);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn resized_to(&mut self, new_size: usize) -> Self {
|
||||
let mut new_node_storage = Self::with_size_in(new_size, self.allocator().clone());
|
||||
|
||||
for mut node in self.nodes.drain(..) {
|
||||
if let Some((key, value, hash)) = node.take_key_value() {
|
||||
new_node_storage.insert_new(key, value, hash);
|
||||
}
|
||||
}
|
||||
|
||||
new_node_storage
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn replace_at_location_unchecked(
|
||||
&mut self,
|
||||
location: usize,
|
||||
key: K,
|
||||
value: V,
|
||||
) -> V {
|
||||
self.node_at_unchecked_mut(location)
|
||||
.replace_unchecked(key, value)
|
||||
.1
|
||||
}
|
||||
|
||||
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut Node<K, V>> {
|
||||
self.nodes.iter_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn node_at(&self, at: usize) -> &Node<K, V> {
|
||||
&self.nodes[at]
|
||||
}
|
||||
|
||||
pub(crate) fn node_at_mut(&mut self, at: usize) -> &mut Node<K, V> {
|
||||
&mut self.nodes[at]
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn node_at_unchecked(&self, at: usize) -> &Node<K, V> {
|
||||
self.nodes.get_unchecked(at)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn node_at_unchecked_mut(&mut self, at: usize) -> &mut Node<K, V> {
|
||||
self.nodes.get_unchecked_mut(at)
|
||||
}
|
||||
|
||||
pub(crate) fn distance_histogram(&self) -> (Vec<usize>, usize) {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for node in self.nodes.iter() {
|
||||
let distance = node.distance();
|
||||
|
||||
if distance >= 0 {
|
||||
let distance = distance as usize;
|
||||
ret.resize(ret.len().max(distance + 1), 0);
|
||||
ret[distance] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
(ret, self.max_distance_to_initial_bucket as usize)
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.max_distance_to_initial_bucket = 0;
|
||||
self.number_of_items = 0;
|
||||
|
||||
self.nodes.fill_with(Node::new);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
authors = ["Gwilym Kuiper <gw@ilym.me>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
@ -12,10 +12,18 @@ proc-macro = true
|
|||
|
||||
[dependencies]
|
||||
image = { version = "0.23", default-features = false, features = [ "png", "bmp" ] }
|
||||
toml = "0.7"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
syn = { version = "2", features = ["full"] }
|
||||
syn = { version = "2", features = ["proc-macro", "parsing"] }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
asefile = "0.3.5"
|
||||
fontdue = "0.7"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
|
@ -1,24 +1,6 @@
|
|||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
use crate::{Colour, Colours, TileSize};
|
||||
|
||||
pub(crate) fn parse(filename: &str) -> Box<dyn Config> {
|
||||
let config_toml =
|
||||
fs::read_to_string(filename).unwrap_or_else(|_| panic!("Failed to read file {filename}"));
|
||||
|
||||
let config: ConfigV1 = toml::from_str(&config_toml).expect("Failed to parse file");
|
||||
|
||||
if config.version != "1.0" {
|
||||
panic!(
|
||||
"Expected version of {} to be 1.0, got {}",
|
||||
filename, config.version
|
||||
);
|
||||
}
|
||||
|
||||
Box::new(config)
|
||||
}
|
||||
use crate::{Colour, Colours};
|
||||
|
||||
pub(crate) trait Config {
|
||||
fn crate_prefix(&self) -> String;
|
||||
|
@ -28,99 +10,5 @@ pub(crate) trait Config {
|
|||
|
||||
pub(crate) trait Image {
|
||||
fn filename(&self) -> String;
|
||||
fn tile_size(&self) -> TileSize;
|
||||
fn colours(&self) -> Colours;
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ConfigV1 {
|
||||
version: String,
|
||||
crate_prefix: Option<String>,
|
||||
transparent_colour: Option<String>,
|
||||
|
||||
image: HashMap<String, ImageV1>,
|
||||
}
|
||||
|
||||
impl Config for ConfigV1 {
|
||||
fn crate_prefix(&self) -> String {
|
||||
self.crate_prefix
|
||||
.clone()
|
||||
.unwrap_or_else(|| "agb".to_owned())
|
||||
}
|
||||
|
||||
fn images(&self) -> HashMap<String, &dyn Image> {
|
||||
self.image
|
||||
.iter()
|
||||
.map(|(filename, image)| (filename.clone(), image as &dyn Image))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn transparent_colour(&self) -> Option<Colour> {
|
||||
if let Some(colour) = &self
|
||||
.transparent_colour
|
||||
.as_ref()
|
||||
.map(|colour| colour.parse().unwrap())
|
||||
{
|
||||
return Some(*colour);
|
||||
}
|
||||
|
||||
self.image
|
||||
.values()
|
||||
.flat_map(|image| image.transparent_colour())
|
||||
.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ImageV1 {
|
||||
filename: String,
|
||||
transparent_colour: Option<String>,
|
||||
tile_size: TileSizeV1,
|
||||
colours: Option<u32>,
|
||||
}
|
||||
|
||||
impl Image for ImageV1 {
|
||||
fn filename(&self) -> String {
|
||||
self.filename.clone()
|
||||
}
|
||||
|
||||
fn tile_size(&self) -> TileSize {
|
||||
self.tile_size.into()
|
||||
}
|
||||
|
||||
fn colours(&self) -> Colours {
|
||||
match self.colours {
|
||||
None | Some(16) => Colours::Colours16,
|
||||
Some(256) => Colours::Colours256,
|
||||
_ => panic!("colours must either not be set or 16 or 256"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageV1 {
|
||||
fn transparent_colour(&self) -> Option<Colour> {
|
||||
self.transparent_colour
|
||||
.as_ref()
|
||||
.map(|colour| colour.parse().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Copy)]
|
||||
pub enum TileSizeV1 {
|
||||
#[serde(rename = "8x8")]
|
||||
Tile8,
|
||||
#[serde(rename = "16x16")]
|
||||
Tile16,
|
||||
#[serde(rename = "32x32")]
|
||||
Tile32,
|
||||
}
|
||||
|
||||
impl From<TileSizeV1> for TileSize {
|
||||
fn from(item: TileSizeV1) -> Self {
|
||||
match item {
|
||||
TileSizeV1::Tile8 => TileSize::Tile8,
|
||||
TileSizeV1::Tile16 => TileSize::Tile16,
|
||||
TileSizeV1::Tile32 => TileSize::Tile32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::path;
|
||||
use std::{ffi::OsStr, path};
|
||||
|
||||
use image::GenericImageView;
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
|
||||
use crate::colour::Colour;
|
||||
|
||||
|
@ -12,7 +12,14 @@ pub(crate) struct Image {
|
|||
|
||||
impl Image {
|
||||
pub fn load_from_file(image_path: &path::Path) -> Self {
|
||||
let img = image::open(image_path).expect("Expected image to exist");
|
||||
let img = if image_path.extension() == Some(OsStr::new("aseprite")) {
|
||||
let ase =
|
||||
asefile::AsepriteFile::read_file(image_path).expect("failed to read aseprite file");
|
||||
DynamicImage::ImageRgba8(ase.frame(0).image())
|
||||
} else {
|
||||
image::open(image_path).expect("Expected image to exist")
|
||||
};
|
||||
|
||||
Self::load_from_dyn_image(img)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ use palette16::{Palette16OptimisationResults, Palette16Optimiser};
|
|||
use palette256::Palette256;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Literal;
|
||||
use syn::parse::Parser;
|
||||
use syn::parse::{Parse, Parser};
|
||||
use syn::{parse_macro_input, punctuated::Punctuated, LitStr};
|
||||
use syn::{Expr, ExprLit, Lit};
|
||||
use syn::{Expr, ExprLit, Lit, Token};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
@ -27,49 +27,139 @@ use image_loader::Image;
|
|||
use colour::Colour;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum TileSize {
|
||||
Tile8,
|
||||
Tile16,
|
||||
Tile32,
|
||||
}
|
||||
|
||||
pub(crate) enum Colours {
|
||||
Colours16,
|
||||
Colours256,
|
||||
}
|
||||
|
||||
impl TileSize {
|
||||
fn to_size(self) -> usize {
|
||||
match self {
|
||||
TileSize::Tile8 => 8,
|
||||
TileSize::Tile16 => 16,
|
||||
TileSize::Tile32 => 32,
|
||||
struct BackgroundGfxOption {
|
||||
module_name: String,
|
||||
file_name: String,
|
||||
colours: Colours,
|
||||
}
|
||||
|
||||
impl config::Image for BackgroundGfxOption {
|
||||
fn filename(&self) -> String {
|
||||
self.file_name.clone()
|
||||
}
|
||||
|
||||
fn colours(&self) -> Colours {
|
||||
self.colours
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for BackgroundGfxOption {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let module_name: syn::Ident = input.parse()?;
|
||||
let _: Token![=>] = input.parse()?;
|
||||
|
||||
let lookahead = input.lookahead1();
|
||||
|
||||
let colours = if lookahead.peek(syn::LitInt) {
|
||||
let num_colours: syn::LitInt = input.parse()?;
|
||||
|
||||
match num_colours.base10_parse()? {
|
||||
16 => Colours::Colours16,
|
||||
256 => Colours::Colours256,
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
num_colours,
|
||||
"Number of colours must be 16 or 256",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Colours::Colours16
|
||||
};
|
||||
|
||||
let file_name: syn::LitStr = input.parse()?;
|
||||
|
||||
Ok(Self {
|
||||
module_name: module_name.to_string(),
|
||||
file_name: file_name.value(),
|
||||
colours,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct IncludeBackgroundGfxInput {
|
||||
module_name: syn::Ident,
|
||||
crate_prefix: String,
|
||||
transparent_colour: Colour,
|
||||
background_gfx_options: Vec<BackgroundGfxOption>,
|
||||
}
|
||||
|
||||
impl Parse for IncludeBackgroundGfxInput {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
|
||||
let crate_prefix: syn::Ident = if lookahead.peek(Token![crate]) {
|
||||
let _: Token![crate] = input.parse()?;
|
||||
let _: Token![,] = input.parse()?;
|
||||
format_ident!("crate")
|
||||
} else {
|
||||
format_ident!("agb")
|
||||
};
|
||||
|
||||
let module_name: syn::Ident = input.parse()?;
|
||||
let _: Token![,] = input.parse()?;
|
||||
|
||||
let lookahead = input.lookahead1();
|
||||
let transparent_colour: Colour = if lookahead.peek(syn::LitStr) {
|
||||
let colour_str: syn::LitStr = input.parse()?;
|
||||
let _: Token![,] = input.parse()?;
|
||||
colour_str
|
||||
.value()
|
||||
.parse()
|
||||
.map_err(|msg| syn::Error::new_spanned(colour_str, msg))?
|
||||
} else {
|
||||
Colour::from_rgb(255, 0, 255, 0)
|
||||
};
|
||||
|
||||
let background_gfx_options =
|
||||
input.parse_terminated(BackgroundGfxOption::parse, Token![,])?;
|
||||
|
||||
Ok(Self {
|
||||
module_name,
|
||||
crate_prefix: crate_prefix.to_string(),
|
||||
transparent_colour,
|
||||
background_gfx_options: background_gfx_options.into_iter().collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl config::Config for IncludeBackgroundGfxInput {
|
||||
fn crate_prefix(&self) -> String {
|
||||
self.crate_prefix.clone()
|
||||
}
|
||||
|
||||
fn images(&self) -> HashMap<String, &dyn config::Image> {
|
||||
self.background_gfx_options
|
||||
.iter()
|
||||
.map(|options| (options.module_name.clone(), options as &dyn config::Image))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn transparent_colour(&self) -> Option<Colour> {
|
||||
Some(self.transparent_colour)
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_gfx(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as syn::LitStr);
|
||||
|
||||
let filename = input.value();
|
||||
pub fn include_background_gfx(input: TokenStream) -> TokenStream {
|
||||
let config = Box::new(parse_macro_input!(input as IncludeBackgroundGfxInput));
|
||||
|
||||
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
|
||||
let path = Path::new(&root).join(&*filename);
|
||||
let parent = path
|
||||
.parent()
|
||||
.expect("Expected a parent directory for the path");
|
||||
|
||||
let config = config::parse(&path.to_string_lossy());
|
||||
|
||||
let module_name = format_ident!(
|
||||
"{}",
|
||||
path.file_stem()
|
||||
.expect("Expected a file stem")
|
||||
.to_string_lossy()
|
||||
);
|
||||
let include_path = path.to_string_lossy();
|
||||
let module_name = config.module_name.clone();
|
||||
include_gfx_from_config(config, module_name, Path::new(&root))
|
||||
}
|
||||
|
||||
fn include_gfx_from_config(
|
||||
config: Box<dyn config::Config>,
|
||||
module_name: syn::Ident,
|
||||
parent: &Path,
|
||||
) -> TokenStream {
|
||||
let images = config.images();
|
||||
|
||||
let mut optimiser = Palette16Optimiser::new(config.transparent_colour());
|
||||
|
@ -84,7 +174,7 @@ pub fn include_gfx(input: TokenStream) -> TokenStream {
|
|||
|
||||
match settings.colours() {
|
||||
Colours::Colours16 => {
|
||||
let tile_size = settings.tile_size().to_size();
|
||||
let tile_size = 8;
|
||||
if image.width % tile_size != 0 || image.height % tile_size != 0 {
|
||||
panic!("Image size not a multiple of tile size");
|
||||
}
|
||||
|
@ -96,7 +186,7 @@ pub fn include_gfx(input: TokenStream) -> TokenStream {
|
|||
config.transparent_colour(),
|
||||
);
|
||||
|
||||
let num_tiles = image.width * image.height / settings.tile_size().to_size().pow(2);
|
||||
let num_tiles = image.width * image.height / 8usize.pow(2);
|
||||
assignment_offsets.insert(name, assignment_offset);
|
||||
assignment_offset += num_tiles;
|
||||
}
|
||||
|
@ -132,8 +222,6 @@ pub fn include_gfx(input: TokenStream) -> TokenStream {
|
|||
|
||||
let module = quote! {
|
||||
mod #module_name {
|
||||
const _: &[u8] = include_bytes!(#include_path);
|
||||
|
||||
#palette_code
|
||||
|
||||
#(#image_code)*
|
||||
|
@ -151,9 +239,36 @@ impl ToTokens for ByteString<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_colours_inner(input: TokenStream) -> TokenStream {
|
||||
let input_filename = parse_macro_input!(input as LitStr);
|
||||
let input_filename = input_filename.value();
|
||||
|
||||
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
|
||||
let input_filename = Path::new(&root).join(input_filename);
|
||||
|
||||
let image = Image::load_from_file(Path::new(&input_filename));
|
||||
|
||||
let mut palette_data = Vec::with_capacity(image.width * image.height);
|
||||
for y in 0..image.height {
|
||||
for x in 0..image.width {
|
||||
palette_data.push(image.colour(x, y).to_rgb15())
|
||||
}
|
||||
}
|
||||
|
||||
let filename = input_filename.to_string_lossy();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
{
|
||||
const _: &[u8] = include_bytes!(#filename);
|
||||
[#(#palette_data),*]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
|
||||
let parser = Punctuated::<LitStr, syn::Token![,]>::parse_separated_nonempty;
|
||||
let parser = Punctuated::<LitStr, syn::Token![,]>::parse_terminated;
|
||||
let parsed = match parser.parse(input) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
|
@ -290,7 +405,6 @@ fn convert_image(
|
|||
optimisation_results,
|
||||
&image,
|
||||
&image_filename.to_string_lossy(),
|
||||
settings.tile_size(),
|
||||
crate_prefix.to_owned(),
|
||||
assignment_offset,
|
||||
)
|
||||
|
@ -344,10 +458,9 @@ fn palette_tile_data(
|
|||
.collect();
|
||||
|
||||
let mut tile_data = Vec::new();
|
||||
let tile_size = TileSize::Tile8;
|
||||
|
||||
for image in images {
|
||||
add_image_to_tile_data(&mut tile_data, image, tile_size, optimiser, 0)
|
||||
for (image_idx, image) in images.iter().enumerate() {
|
||||
add_image_to_tile_data(&mut tile_data, image, optimiser, image_idx)
|
||||
}
|
||||
|
||||
let tile_data = collapse_to_4bpp(&tile_data);
|
||||
|
@ -367,11 +480,10 @@ fn collapse_to_4bpp(tile_data: &[u8]) -> Vec<u8> {
|
|||
fn add_image_to_tile_data(
|
||||
tile_data: &mut Vec<u8>,
|
||||
image: &Image,
|
||||
tile_size: TileSize,
|
||||
optimiser: &Palette16OptimisationResults,
|
||||
assignment_offset: usize,
|
||||
) {
|
||||
let tile_size = tile_size.to_size();
|
||||
let tile_size = 8;
|
||||
let tiles_x = image.width / tile_size;
|
||||
let tiles_y = image.height / tile_size;
|
||||
|
||||
|
@ -397,10 +509,9 @@ fn add_image_to_tile_data(
|
|||
fn add_image_256_to_tile_data(
|
||||
tile_data: &mut Vec<u8>,
|
||||
image: &Image,
|
||||
tile_size: TileSize,
|
||||
optimiser: &Palette16OptimisationResults,
|
||||
) {
|
||||
let tile_size = tile_size.to_size();
|
||||
let tile_size = 8;
|
||||
let tiles_x = image.width / tile_size;
|
||||
let tiles_y = image.height / tile_size;
|
||||
|
||||
|
@ -485,21 +596,6 @@ pub fn include_font(input: TokenStream) -> TokenStream {
|
|||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use asefile::AnimationDirection;
|
||||
|
||||
#[test]
|
||||
// These directions defined in agb and have these values. This is important
|
||||
// when outputting code for agb. If more animation directions are added then
|
||||
// we will have to support them there.
|
||||
fn directions_to_agb() {
|
||||
assert_eq!(AnimationDirection::Forward as usize, 0);
|
||||
assert_eq!(AnimationDirection::Reverse as usize, 1);
|
||||
assert_eq!(AnimationDirection::PingPong as usize, 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_sprite_size(width: u32, height: u32) -> bool {
|
||||
match (width, height) {
|
||||
(8, 8) => true,
|
||||
|
@ -517,3 +613,18 @@ fn valid_sprite_size(width: u32, height: u32) -> bool {
|
|||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use asefile::AnimationDirection;
|
||||
|
||||
#[test]
|
||||
// These directions defined in agb and have these values. This is important
|
||||
// when outputting code for agb. If more animation directions are added then
|
||||
// we will have to support them there.
|
||||
fn directions_to_agb() {
|
||||
assert_eq!(AnimationDirection::Forward as usize, 0);
|
||||
assert_eq!(AnimationDirection::Reverse as usize, 1);
|
||||
assert_eq!(AnimationDirection::PingPong as usize, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,17 +138,7 @@ impl Palette16Optimiser {
|
|||
while !unsatisfied_palettes.is_empty() {
|
||||
let palette = self.find_maximal_palette_for(&unsatisfied_palettes);
|
||||
|
||||
for test_palette in unsatisfied_palettes.clone() {
|
||||
if test_palette.is_satisfied_by(&palette) {
|
||||
unsatisfied_palettes.remove(&test_palette);
|
||||
}
|
||||
}
|
||||
|
||||
for (i, overall_palette) in self.palettes.iter().enumerate() {
|
||||
if overall_palette.is_satisfied_by(&palette) {
|
||||
assignments[i] = optimised_palettes.len();
|
||||
}
|
||||
}
|
||||
unsatisfied_palettes.retain(|test_palette| !test_palette.is_satisfied_by(&palette));
|
||||
|
||||
optimised_palettes.push(palette);
|
||||
|
||||
|
@ -157,6 +147,13 @@ impl Palette16Optimiser {
|
|||
}
|
||||
}
|
||||
|
||||
for (i, overall_palette) in self.palettes.iter().enumerate() {
|
||||
assignments[i] = optimised_palettes
|
||||
.iter()
|
||||
.position(|palette| overall_palette.is_satisfied_by(palette))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Palette16OptimisationResults {
|
||||
optimised_palettes,
|
||||
assignments,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::palette16::Palette16OptimisationResults;
|
||||
use crate::{add_image_256_to_tile_data, add_image_to_tile_data, collapse_to_4bpp, TileSize};
|
||||
use crate::{add_image_256_to_tile_data, add_image_to_tile_data, collapse_to_4bpp};
|
||||
use crate::{image_loader::Image, ByteString};
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
|
@ -38,7 +38,6 @@ pub(crate) fn generate_code(
|
|||
results: &Palette16OptimisationResults,
|
||||
image: &Image,
|
||||
image_filename: &str,
|
||||
tile_size: TileSize,
|
||||
crate_prefix: String,
|
||||
assignment_offset: Option<usize>,
|
||||
) -> TokenStream {
|
||||
|
@ -48,11 +47,11 @@ pub(crate) fn generate_code(
|
|||
let (tile_data, assignments) = if let Some(assignment_offset) = assignment_offset {
|
||||
let mut tile_data = Vec::new();
|
||||
|
||||
add_image_to_tile_data(&mut tile_data, image, tile_size, results, assignment_offset);
|
||||
add_image_to_tile_data(&mut tile_data, image, results, assignment_offset);
|
||||
|
||||
let tile_data = collapse_to_4bpp(&tile_data);
|
||||
|
||||
let num_tiles = image.width * image.height / tile_size.to_size().pow(2);
|
||||
let num_tiles = image.width * image.height / 8usize.pow(2);
|
||||
|
||||
let assignments = results
|
||||
.assignments
|
||||
|
@ -66,7 +65,7 @@ pub(crate) fn generate_code(
|
|||
} else {
|
||||
let mut tile_data = Vec::new();
|
||||
|
||||
add_image_256_to_tile_data(&mut tile_data, image, tile_size, results);
|
||||
add_image_256_to_tile_data(&mut tile_data, image, results);
|
||||
|
||||
(tile_data, vec![])
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "agb_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
authors = ["Gwilym Kuiper <gw@ilym.me>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
@ -14,3 +14,13 @@ proc-macro = true
|
|||
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
[package]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
authors = ["Gwilym Kuiper <gw@ilym.me>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
description = "Library for converting wavs for use on the Game Boy Advance"
|
||||
repository = "https://github.com/agbrs/agb"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
debug = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
|
@ -23,3 +15,13 @@ hound = "3.5"
|
|||
syn = "2"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
|
@ -1,12 +1,54 @@
|
|||
[package]
|
||||
name = "agb"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
authors = ["Corwin Kuiper <corwin@kuiper.dev>", "Gwilym Kuiper <gw@ilym.me>"]
|
||||
edition = "2021"
|
||||
description = "Library for Game Boy Advance Development"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/agbrs/agb"
|
||||
|
||||
[features]
|
||||
default = ["testing"]
|
||||
testing = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
agb_image_converter = { version = "0.15.0", path = "../agb-image-converter" }
|
||||
agb_sound_converter = { version = "0.15.0", path = "../agb-sound-converter" }
|
||||
agb_macros = { version = "0.15.0", path = "../agb-macros" }
|
||||
agb_fixnum = { version = "0.15.0", path = "../agb-fixnum" }
|
||||
agb_hashmap = { version = "0.15.0", path = "../agb-hashmap" }
|
||||
bare-metal = "1"
|
||||
modular-bitfield = "0.11"
|
||||
rustc-hash = { version = "1", default-features = false }
|
||||
embedded-hal = "0.2.7"
|
||||
nb = "1.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "thumbv6m-none-eabi"
|
||||
targets = []
|
||||
|
||||
[features]
|
||||
default = ["testing"]
|
||||
testing = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
agb_image_converter = { version = "0.15.0", path = "../agb-image-converter" }
|
||||
agb_sound_converter = { version = "0.15.0", path = "../agb-sound-converter" }
|
||||
agb_macros = { version = "0.15.0", path = "../agb-macros" }
|
||||
agb_fixnum = { version = "0.15.0", path = "../agb-fixnum" }
|
||||
agb_hashmap = { version = "0.15.0", path = "../agb-hashmap" }
|
||||
bare-metal = "1"
|
||||
modular-bitfield = "0.11"
|
||||
rustc-hash = { version = "1", default-features = false }
|
||||
embedded-hal = "0.2.7"
|
||||
nb = "1.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "thumbv6m-none-eabi"
|
||||
targets = []
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
@ -16,23 +58,3 @@ opt-level = 3
|
|||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
||||
[features]
|
||||
default = ["testing"]
|
||||
testing = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
agb_image_converter = { version = "0.14.0", path = "../agb-image-converter" }
|
||||
agb_sound_converter = { version = "0.14.0", path = "../agb-sound-converter" }
|
||||
agb_macros = { version = "0.14.0", path = "../agb-macros" }
|
||||
agb_fixnum = { version = "0.14.0", path = "../agb-fixnum" }
|
||||
bare-metal = "1"
|
||||
modular-bitfield = "0.11"
|
||||
rustc-hash = { version = "1", default-features = false }
|
||||
embedded-hal = "0.2.7"
|
||||
nb = "1.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "thumbv6m-none-eabi"
|
||||
targets = []
|
||||
|
|
|
@ -8,10 +8,10 @@ use agb::{
|
|||
Priority,
|
||||
},
|
||||
fixnum::{num, Num},
|
||||
include_gfx,
|
||||
include_background_gfx,
|
||||
};
|
||||
|
||||
include_gfx!("examples/affine_tiles.toml");
|
||||
include_background_gfx!(affine_tiles, water_tiles => 256 "examples/water_tiles.png");
|
||||
|
||||
#[agb::entry]
|
||||
fn main(mut gba: agb::Gba) -> ! {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
version = "1.0"
|
||||
|
||||
[image.water_tiles]
|
||||
filename = "water_tiles.png"
|
||||
tile_size = "8x8"
|
||||
colours = 256
|
|
@ -6,10 +6,10 @@ use agb::{
|
|||
tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap},
|
||||
Priority,
|
||||
},
|
||||
include_gfx,
|
||||
include_background_gfx,
|
||||
};
|
||||
|
||||
include_gfx!("examples/water_tiles.toml");
|
||||
include_background_gfx!(water_tiles, water_tiles => "examples/water_tiles.png");
|
||||
|
||||
#[agb::entry]
|
||||
fn main(mut gba: agb::Gba) -> ! {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use agb::{
|
||||
display::tiled::{TileFormat, TileSet, TileSetting, TiledMap},
|
||||
display::{
|
||||
object::{Object, ObjectController, Size, Sprite},
|
||||
object::{OamManaged, Object, Size, Sprite},
|
||||
palette16::Palette16,
|
||||
tiled::RegularBackgroundSize,
|
||||
HEIGHT, WIDTH,
|
||||
|
@ -74,7 +74,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
background.show();
|
||||
background.commit(&mut vram);
|
||||
|
||||
let object = gba.display.object.get();
|
||||
let object = gba.display.object.get_managed();
|
||||
|
||||
let sprite = object.sprite(&CHICKEN_SPRITES[0]);
|
||||
let mut chicken = Character {
|
||||
|
@ -143,9 +143,9 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_chicken_object<'a>(
|
||||
chicken: &'_ mut Character<'a>,
|
||||
object: &'a ObjectController,
|
||||
fn update_chicken_object(
|
||||
chicken: &'_ mut Character<'_>,
|
||||
gfx: &OamManaged,
|
||||
state: State,
|
||||
frame_count: u32,
|
||||
) {
|
||||
|
@ -157,20 +157,18 @@ fn update_chicken_object<'a>(
|
|||
match state {
|
||||
State::Ground => {
|
||||
if chicken.velocity.x.abs() > 1 << 4 {
|
||||
chicken.object.set_sprite(
|
||||
object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]),
|
||||
);
|
||||
} else {
|
||||
chicken
|
||||
.object
|
||||
.set_sprite(object.sprite(&CHICKEN_SPRITES[0]));
|
||||
.set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]));
|
||||
} else {
|
||||
chicken.object.set_sprite(gfx.sprite(&CHICKEN_SPRITES[0]));
|
||||
}
|
||||
}
|
||||
State::Upwards => {}
|
||||
State::Flapping => {
|
||||
chicken
|
||||
.object
|
||||
.set_sprite(object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)]));
|
||||
.set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
7
agb/examples/no_game.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
#[agb::entry]
|
||||
fn main(gba: agb::Gba) -> ! {
|
||||
agb::no_game(gba);
|
||||
}
|
|
@ -3,7 +3,12 @@
|
|||
|
||||
extern crate alloc;
|
||||
|
||||
use agb::display::object::{Graphics, ObjectController, Sprite, TagMap};
|
||||
use agb::display::{
|
||||
affine::AffineMatrix,
|
||||
object::{self, Graphics, OamManaged, Sprite, TagMap},
|
||||
};
|
||||
use agb::fixnum::num;
|
||||
use agb_fixnum::Num;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
const GRAPHICS: &Graphics = agb::include_aseprite!(
|
||||
|
@ -15,14 +20,20 @@ const GRAPHICS: &Graphics = agb::include_aseprite!(
|
|||
const SPRITES: &[Sprite] = GRAPHICS.sprites();
|
||||
const TAG_MAP: &TagMap = GRAPHICS.tags();
|
||||
|
||||
fn all_sprites(gfx: &ObjectController) {
|
||||
fn all_sprites(gfx: &OamManaged, rotation_speed: Num<i32, 16>) {
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
let mut objs = Vec::new();
|
||||
|
||||
let mut rotation: Num<i32, 16> = num!(0.);
|
||||
|
||||
let rotation_matrix = AffineMatrix::from_rotation(rotation);
|
||||
let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping());
|
||||
|
||||
for y in 0..9 {
|
||||
for x in 0..14 {
|
||||
let mut obj = gfx.object(gfx.sprite(&SPRITES[0]));
|
||||
obj.show();
|
||||
let mut obj = gfx.object_sprite(&SPRITES[0]);
|
||||
obj.set_affine_matrix(matrix.clone());
|
||||
obj.show_affine(object::AffineMode::Affine);
|
||||
obj.set_position((x * 16 + 8, y * 16 + 8).into());
|
||||
objs.push(obj);
|
||||
}
|
||||
|
@ -41,6 +52,15 @@ fn all_sprites(gfx: &ObjectController) {
|
|||
break;
|
||||
}
|
||||
|
||||
rotation += rotation_speed;
|
||||
let rotation_matrix = AffineMatrix::from_rotation(rotation);
|
||||
|
||||
let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping());
|
||||
|
||||
for obj in objs.iter_mut() {
|
||||
obj.set_affine_matrix(matrix.clone());
|
||||
}
|
||||
|
||||
count += 1;
|
||||
|
||||
if count % 5 == 0 {
|
||||
|
@ -50,12 +70,12 @@ fn all_sprites(gfx: &ObjectController) {
|
|||
let this_image = (image + i) % SPRITES.len();
|
||||
obj.set_sprite(gfx.sprite(&SPRITES[this_image]));
|
||||
}
|
||||
}
|
||||
gfx.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn all_tags(gfx: &ObjectController) {
|
||||
fn all_tags(gfx: &OamManaged) {
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
let mut objs = Vec::new();
|
||||
|
||||
|
@ -65,7 +85,7 @@ fn all_tags(gfx: &ObjectController) {
|
|||
let sprite = v.sprite(0);
|
||||
let (size_x, size_y) = sprite.size().to_width_height();
|
||||
let (size_x, size_y) = (size_x as i32, size_y as i32);
|
||||
let mut obj = gfx.object(gfx.sprite(sprite));
|
||||
let mut obj = gfx.object_sprite(sprite);
|
||||
obj.show();
|
||||
obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into());
|
||||
objs.push((obj, v));
|
||||
|
@ -99,12 +119,11 @@ fn all_tags(gfx: &ObjectController) {
|
|||
|
||||
#[agb::entry]
|
||||
fn main(mut gba: agb::Gba) -> ! {
|
||||
let gfx = gba.display.object.get();
|
||||
let gfx = gba.display.object.get_managed();
|
||||
|
||||
loop {
|
||||
all_tags(&gfx);
|
||||
gfx.commit();
|
||||
all_sprites(&gfx);
|
||||
gfx.commit();
|
||||
all_sprites(&gfx, num!(0.));
|
||||
all_sprites(&gfx, num!(0.01));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
version = "1.0"
|
||||
|
||||
# Only needed for within the agb crate
|
||||
crate_prefix = "crate"
|
||||
|
||||
[image.test_logo]
|
||||
filename = "test_logo.png"
|
||||
tile_size = "8x8"
|
BIN
agb/gfx/pastel.png
Normal file
After Width: | Height: | Size: 355 B |
|
@ -120,15 +120,6 @@ pub(crate) unsafe fn number_of_blocks() -> u32 {
|
|||
GLOBAL_ALLOC.number_of_blocks()
|
||||
}
|
||||
|
||||
#[alloc_error_handler]
|
||||
fn alloc_error(layout: Layout) -> ! {
|
||||
panic!(
|
||||
"Failed to allocate size {} with alignment {}",
|
||||
layout.size(),
|
||||
layout.align()
|
||||
);
|
||||
}
|
||||
|
||||
fn iwram_data_end() -> usize {
|
||||
extern "C" {
|
||||
static __iwram_end: usize;
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
.include "src/agbabi/macros.inc"
|
||||
|
||||
.arm
|
||||
.align 2
|
||||
|
||||
.section .iwram.__aeabi_memcpy, "ax", %progbits
|
||||
.align 2
|
||||
.global __agbabi_memcpy
|
||||
__agbabi_memcpy:
|
||||
.global __aeabi_memcpy
|
||||
|
@ -115,6 +115,7 @@ __agbabi_memcpy1:
|
|||
bx lr
|
||||
|
||||
.section .iwram.memcpy, "ax", %progbits
|
||||
.align 2
|
||||
.global memcpy
|
||||
memcpy:
|
||||
push {r0, lr}
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
*/
|
||||
|
||||
.arm
|
||||
.align 2
|
||||
|
||||
.section .iwram.__aeabi_memset, "ax", %progbits
|
||||
.align 2
|
||||
.global __aeabi_memclr
|
||||
__aeabi_memclr:
|
||||
mov r2, #0
|
||||
|
@ -109,6 +109,7 @@ __agbabi_wordset4:
|
|||
bx lr
|
||||
|
||||
.section .iwram.memset, "ax", %progbits
|
||||
.align 2
|
||||
.global memset
|
||||
memset:
|
||||
mov r3, r1
|
||||
|
|
77
agb/src/arena.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use core::{alloc::Allocator, mem::ManuallyDrop};
|
||||
|
||||
use alloc::{alloc::Global, vec::Vec};
|
||||
|
||||
union ArenaItem<T> {
|
||||
free: Option<ArenaKey>,
|
||||
occupied: ManuallyDrop<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ArenaKey(usize);
|
||||
|
||||
pub struct Arena<T, A: Allocator = Global> {
|
||||
tip: Option<ArenaKey>,
|
||||
data: Vec<ArenaItem<T>, A>,
|
||||
inserted: usize,
|
||||
}
|
||||
|
||||
impl<T> Arena<T, Global> {
|
||||
pub const fn new() -> Self {
|
||||
Self::new_in(Global)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, A: Allocator> Arena<T, A> {
|
||||
pub const fn new_in(alloc: A) -> Self {
|
||||
Self {
|
||||
tip: None,
|
||||
data: Vec::new_in(alloc),
|
||||
inserted: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn insert(&mut self, value: T) -> ArenaKey {
|
||||
self.inserted += 1;
|
||||
match self.tip {
|
||||
Some(tip) => {
|
||||
self.tip = self.data[tip.0].free;
|
||||
self.data[tip.0].occupied = ManuallyDrop::new(value);
|
||||
tip
|
||||
}
|
||||
None => {
|
||||
self.data.push(ArenaItem {
|
||||
occupied: ManuallyDrop::new(value),
|
||||
});
|
||||
ArenaKey(self.data.len() - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn remove(&mut self, key: ArenaKey) {
|
||||
self.inserted = self
|
||||
.inserted
|
||||
.checked_sub(1)
|
||||
.expect("removed more items than exist in here!");
|
||||
|
||||
unsafe {
|
||||
core::mem::ManuallyDrop::<T>::drop(&mut self.data[key.0].occupied);
|
||||
}
|
||||
|
||||
self.data[key.0].free = self.tip;
|
||||
self.tip = Some(key);
|
||||
}
|
||||
|
||||
pub unsafe fn get(&self, key: ArenaKey) -> &T {
|
||||
&self.data[key.0].occupied
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, A: Allocator> Drop for Arena<T, A> {
|
||||
fn drop(&mut self) {
|
||||
assert_eq!(
|
||||
self.inserted, 0,
|
||||
"must remove all elements from arena before dropping it!"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
.macro agb_thumb_func functionName:req
|
||||
.section .iwram.\functionName, "ax", %progbits
|
||||
.thumb
|
||||
.align 2
|
||||
.align 1
|
||||
.global \functionName
|
||||
.type \functionName, %function
|
||||
.func \functionName
|
||||
|
|
|
@ -356,6 +356,15 @@ impl AffineMatrixObject {
|
|||
y: 0.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn components(self) -> [u16; 4] {
|
||||
[
|
||||
self.a.to_raw() as u16,
|
||||
self.b.to_raw() as u16,
|
||||
self.c.to_raw() as u16,
|
||||
self.d.to_raw() as u16,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AffineMatrixObject> for AffineMatrix {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager};
|
||||
|
||||
crate::include_gfx!("gfx/agb_logo.toml");
|
||||
crate::include_background_gfx!(crate, agb_logo, test_logo => "gfx/test_logo.png");
|
||||
|
||||
pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
|
||||
vram.set_background_palettes(agb_logo::PALETTES);
|
||||
|
|
|
@ -4,7 +4,11 @@ use bitflags::bitflags;
|
|||
use modular_bitfield::BitfieldSpecifier;
|
||||
use video::Video;
|
||||
|
||||
use self::{blend::Blend, object::ObjectController, window::Windows};
|
||||
use self::{
|
||||
blend::Blend,
|
||||
object::{initilise_oam, OamManaged, OamUnmanaged, SpriteLoader},
|
||||
window::Windows,
|
||||
};
|
||||
|
||||
/// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer.
|
||||
pub mod bitmap3;
|
||||
|
@ -12,7 +16,6 @@ pub mod bitmap3;
|
|||
pub mod bitmap4;
|
||||
/// Test logo of agb.
|
||||
pub mod example_logo;
|
||||
/// Implements sprites.
|
||||
pub mod object;
|
||||
/// Palette type.
|
||||
pub mod palette16;
|
||||
|
@ -80,8 +83,21 @@ pub struct Display {
|
|||
pub struct ObjectDistribution;
|
||||
|
||||
impl ObjectDistribution {
|
||||
pub fn get(&mut self) -> ObjectController<'_> {
|
||||
ObjectController::new()
|
||||
pub fn get_unmanaged(&mut self) -> (OamUnmanaged<'_>, SpriteLoader) {
|
||||
unsafe { initilise_oam() };
|
||||
(OamUnmanaged::new(), SpriteLoader::new())
|
||||
}
|
||||
|
||||
pub fn get_managed(&mut self) -> OamManaged<'_> {
|
||||
unsafe { initilise_oam() };
|
||||
OamManaged::new()
|
||||
}
|
||||
|
||||
/// The old name for [`get_managed`][ObjectDistribution::get_managed] kept around for easier migration.
|
||||
/// This will be removed in a future release.
|
||||
#[deprecated = "use get_managed to get the managed oam instead"]
|
||||
pub fn get(&mut self) -> OamManaged<'_> {
|
||||
self.get_managed()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +159,7 @@ pub fn busy_wait_for_vblank() {
|
|||
while VCOUNT.get() < 160 {}
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier, Clone, Copy)]
|
||||
#[derive(BitfieldSpecifier, Clone, Copy, Debug)]
|
||||
pub enum Priority {
|
||||
P0 = 0,
|
||||
P1 = 1,
|
||||
|
|
87
agb/src/display/object/affine.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use core::cell::Cell;
|
||||
|
||||
use alloc::rc::Rc;
|
||||
|
||||
use crate::display::affine::AffineMatrixObject;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AffineMatrixData {
|
||||
frame_count: Cell<u32>,
|
||||
location: Cell<u32>,
|
||||
matrix: AffineMatrixObject,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AffineMatrixVram(Rc<AffineMatrixData>);
|
||||
|
||||
/// An affine matrix that can be used on objects. It is just in time copied to
|
||||
/// vram, so you can have as many as you like of these but you can only use up
|
||||
/// to 16 in one frame. They are reference counted (Cloning is cheap) and
|
||||
/// immutable, if you want to change a matrix you must make a new one and set it
|
||||
/// on all your objects.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AffineMatrixInstance {
|
||||
location: AffineMatrixVram,
|
||||
}
|
||||
|
||||
impl AffineMatrixInstance {
|
||||
#[must_use]
|
||||
/// Creates an instance of an affine matrix from its object form. Check out
|
||||
/// the docs for [AffineMatrix][crate::display::affine::AffineMatrix] to see
|
||||
/// how you can use them to create effects.
|
||||
pub fn new(affine_matrix: AffineMatrixObject) -> AffineMatrixInstance {
|
||||
AffineMatrixInstance {
|
||||
location: AffineMatrixVram(Rc::new(AffineMatrixData {
|
||||
frame_count: Cell::new(u32::MAX),
|
||||
location: Cell::new(u32::MAX),
|
||||
matrix: affine_matrix,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vram(self) -> AffineMatrixVram {
|
||||
self.location
|
||||
}
|
||||
}
|
||||
|
||||
impl AffineMatrixVram {
|
||||
pub fn frame_count(&self) -> u32 {
|
||||
self.0.frame_count.get()
|
||||
}
|
||||
|
||||
pub fn set_frame_count(&self, frame: u32) {
|
||||
self.0.frame_count.set(frame);
|
||||
}
|
||||
|
||||
pub fn location(&self) -> u32 {
|
||||
self.0.location.get()
|
||||
}
|
||||
|
||||
pub fn set_location(&self, location: u32) {
|
||||
self.0.location.set(location);
|
||||
}
|
||||
|
||||
pub fn write_to_location(&self, oam: *mut u16) {
|
||||
let components = self.0.matrix.components();
|
||||
let location = self.0.location.get() as usize;
|
||||
for (idx, component) in components.iter().enumerate() {
|
||||
unsafe {
|
||||
oam.add(location * 16 + idx * 4 + 3)
|
||||
.write_volatile(*component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test_case]
|
||||
fn niche_optimisation(_gba: &mut crate::Gba) {
|
||||
assert_eq!(
|
||||
core::mem::size_of::<AffineMatrixInstance>(),
|
||||
core::mem::size_of::<Option<AffineMatrixInstance>>()
|
||||
);
|
||||
}
|
||||
}
|
511
agb/src/display/object/managed.rs
Normal file
|
@ -0,0 +1,511 @@
|
|||
use core::cell::{Cell, UnsafeCell};
|
||||
|
||||
use agb_fixnum::Vector2D;
|
||||
|
||||
use crate::{
|
||||
arena::{Arena, ArenaKey},
|
||||
display::Priority,
|
||||
};
|
||||
|
||||
use super::{
|
||||
AffineMatrixInstance, AffineMode, OamUnmanaged, ObjectUnmanaged, Sprite, SpriteLoader,
|
||||
SpriteVram,
|
||||
};
|
||||
|
||||
type ObjectKey = ArenaKey;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Ordering {
|
||||
next: Option<ObjectKey>,
|
||||
previous: Option<ObjectKey>,
|
||||
}
|
||||
|
||||
struct ObjectItem {
|
||||
object: UnsafeCell<ObjectUnmanaged>,
|
||||
z_order: Cell<Ordering>,
|
||||
z_index: Cell<i32>,
|
||||
}
|
||||
|
||||
struct Store {
|
||||
store: UnsafeCell<Arena<ObjectItem>>,
|
||||
first_z: Cell<Option<ObjectKey>>,
|
||||
}
|
||||
|
||||
struct StoreIterator<'store> {
|
||||
store: &'store Arena<ObjectItem>,
|
||||
current: Option<ObjectKey>,
|
||||
}
|
||||
|
||||
impl<'store> Iterator for StoreIterator<'store> {
|
||||
type Item = &'store ObjectItem;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let to_output = unsafe { self.store.get(self.current?) };
|
||||
self.current = to_output.z_order.get().next;
|
||||
Some(to_output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// SAFETY: while this exists, no other store related operations should be
|
||||
/// performed. Notably this means you shouldn't drop the ObjectItem as this
|
||||
/// implementation will touch this.
|
||||
unsafe fn iter(&self) -> StoreIterator {
|
||||
StoreIterator {
|
||||
store: unsafe { &*self.store.get() },
|
||||
current: self.first_z.get(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn is_all_ordered_right(&self) -> bool {
|
||||
let mut previous_z = i32::MIN;
|
||||
let mut current_index = self.first_z.get();
|
||||
|
||||
while let Some(ci) = current_index {
|
||||
let obj = self.get_object(ci);
|
||||
let this_z = obj.z_index.get();
|
||||
if this_z < previous_z {
|
||||
return false;
|
||||
}
|
||||
previous_z = this_z;
|
||||
current_index = obj.z_order.get().next;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn insert_object(&self, object: ObjectUnmanaged) -> Object {
|
||||
let object_item = ObjectItem {
|
||||
object: UnsafeCell::new(object),
|
||||
z_order: Cell::new(Ordering {
|
||||
next: None,
|
||||
previous: None,
|
||||
}),
|
||||
z_index: Cell::new(0),
|
||||
};
|
||||
let idx = {
|
||||
let data = unsafe { &mut *self.store.get() };
|
||||
unsafe { data.insert(object_item) }
|
||||
};
|
||||
|
||||
if let Some(first) = self.first_z.get() {
|
||||
let mut this_index = first;
|
||||
while self.get_object(this_index).z_index.get() < 0 {
|
||||
if let Some(idx) = self.get_object(this_index).z_order.get().next {
|
||||
this_index = idx;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if self.get_object(this_index).z_index.get() < 0 {
|
||||
add_after_element(self, idx, this_index);
|
||||
} else {
|
||||
add_before_element(self, idx, this_index);
|
||||
}
|
||||
} else {
|
||||
self.first_z.set(Some(idx));
|
||||
}
|
||||
|
||||
Object {
|
||||
me: idx,
|
||||
store: self,
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_object(&self, object: ObjectKey) {
|
||||
remove_from_linked_list(self, object);
|
||||
|
||||
let data = unsafe { &mut *self.store.get() };
|
||||
unsafe { data.remove(object) };
|
||||
}
|
||||
|
||||
fn get_object(&self, key: ObjectKey) -> &ObjectItem {
|
||||
unsafe { (*self.store.get()).get(key) }
|
||||
}
|
||||
}
|
||||
|
||||
/// OAM that manages z ordering and commit all visible objects in one call. This
|
||||
/// is simpler to use than the [`OamUnmanaged`], but is less performant
|
||||
/// depending on how objects are stored.
|
||||
///
|
||||
/// Use this if:
|
||||
/// * You don't want to handle z ordering.
|
||||
/// * You don't want to deal with the complexity of committing all objects during vblank.
|
||||
///
|
||||
/// Otherwise I'd recommend using [`OamUnmanaged`].
|
||||
pub struct OamManaged<'gba> {
|
||||
object_store: Store,
|
||||
sprite_loader: UnsafeCell<SpriteLoader>,
|
||||
unmanaged: UnsafeCell<OamUnmanaged<'gba>>,
|
||||
}
|
||||
|
||||
impl OamManaged<'_> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
object_store: Store {
|
||||
store: UnsafeCell::new(Arena::new()),
|
||||
first_z: Cell::new(None),
|
||||
},
|
||||
sprite_loader: UnsafeCell::new(SpriteLoader::new()),
|
||||
unmanaged: UnsafeCell::new(OamUnmanaged::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY:
|
||||
/// Do not reenter or recurse or otherwise use sprite loader cell during this.
|
||||
unsafe fn do_work_with_sprite_loader<C, T>(&self, c: C) -> T
|
||||
where
|
||||
C: Fn(&mut SpriteLoader) -> T,
|
||||
{
|
||||
let sprite_loader = unsafe { &mut *self.sprite_loader.get() };
|
||||
|
||||
c(sprite_loader)
|
||||
}
|
||||
|
||||
/// Commits all the visible objects. Call during vblank to make changes made
|
||||
/// to objects visible.
|
||||
pub fn commit(&self) {
|
||||
// safety: commit is not reentrant
|
||||
let unmanaged = unsafe { &mut *self.unmanaged.get() };
|
||||
|
||||
for (object, slot) in unsafe { self.object_store.iter() }
|
||||
.map(|item| unsafe { &*item.object.get() })
|
||||
.filter(|object| object.is_visible())
|
||||
.zip(unmanaged.iter())
|
||||
{
|
||||
slot.set(object);
|
||||
}
|
||||
|
||||
// safety: not reentrant
|
||||
unsafe {
|
||||
self.do_work_with_sprite_loader(SpriteLoader::garbage_collect);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an object from the sprite in vram.
|
||||
pub fn object(&self, sprite: SpriteVram) -> Object<'_> {
|
||||
self.object_store
|
||||
.insert_object(ObjectUnmanaged::new(sprite))
|
||||
}
|
||||
|
||||
/// Creates a sprite in vram from a static sprite from [`include_aseprite`][crate::include_aseprite].
|
||||
pub fn sprite(&self, sprite: &'static Sprite) -> SpriteVram {
|
||||
// safety: not reentrant
|
||||
unsafe {
|
||||
self.do_work_with_sprite_loader(|sprite_loader| sprite_loader.get_vram_sprite(sprite))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a sprite in vram and uses it to make an object from a static sprite from [`include_aseprite`][crate::include_aseprite].
|
||||
pub fn object_sprite(&self, sprite: &'static Sprite) -> Object<'_> {
|
||||
self.object(self.sprite(sprite))
|
||||
}
|
||||
}
|
||||
|
||||
/// A managed object used with the [`OamManaged`] interface.
|
||||
pub struct Object<'controller> {
|
||||
me: ObjectKey,
|
||||
store: &'controller Store,
|
||||
}
|
||||
|
||||
impl Drop for Object<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.store.remove_object(self.me);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_from_linked_list(store: &Store, to_remove: ObjectKey) {
|
||||
let my_current_neighbours = store.get_object(to_remove).z_order.get();
|
||||
|
||||
if let Some(previous) = my_current_neighbours.previous {
|
||||
let stored_part = &store.get_object(previous).z_order;
|
||||
let mut neighbour_left = stored_part.get();
|
||||
neighbour_left.next = my_current_neighbours.next;
|
||||
stored_part.set(neighbour_left);
|
||||
} else {
|
||||
store.first_z.set(my_current_neighbours.next);
|
||||
}
|
||||
|
||||
if let Some(next) = my_current_neighbours.next {
|
||||
let stored_part = &store.get_object(next).z_order;
|
||||
let mut neighbour_right = stored_part.get();
|
||||
neighbour_right.previous = my_current_neighbours.previous;
|
||||
stored_part.set(neighbour_right);
|
||||
}
|
||||
|
||||
store.get_object(to_remove).z_order.set(Ordering {
|
||||
next: None,
|
||||
previous: None,
|
||||
});
|
||||
}
|
||||
|
||||
fn add_before_element(store: &Store, elem: ObjectKey, before_this: ObjectKey) {
|
||||
assert_ne!(elem, before_this);
|
||||
|
||||
let this_element_store = &store.get_object(elem).z_order;
|
||||
let mut this_element = this_element_store.get();
|
||||
|
||||
let before_store = &store.get_object(before_this).z_order;
|
||||
let mut before = before_store.get();
|
||||
|
||||
if let Some(previous) = before.previous {
|
||||
let neighbour_left_store = &store.get_object(previous).z_order;
|
||||
let mut neighbour_left = neighbour_left_store.get();
|
||||
neighbour_left.next = Some(elem);
|
||||
neighbour_left_store.set(neighbour_left);
|
||||
} else {
|
||||
store.first_z.set(Some(elem));
|
||||
}
|
||||
this_element.next = Some(before_this);
|
||||
this_element.previous = before.previous;
|
||||
|
||||
before.previous = Some(elem);
|
||||
|
||||
this_element_store.set(this_element);
|
||||
before_store.set(before);
|
||||
}
|
||||
|
||||
fn add_after_element(store: &Store, elem: ObjectKey, after_this: ObjectKey) {
|
||||
assert_ne!(elem, after_this);
|
||||
|
||||
let this_element_store = &store.get_object(elem).z_order;
|
||||
let mut this_element = this_element_store.get();
|
||||
|
||||
let after_store = &store.get_object(after_this).z_order;
|
||||
let mut after = after_store.get();
|
||||
|
||||
if let Some(next) = after.next {
|
||||
let neighbour_left_store = &store.get_object(next).z_order;
|
||||
let mut neighbour_right = neighbour_left_store.get();
|
||||
neighbour_right.previous = Some(elem);
|
||||
neighbour_left_store.set(neighbour_right);
|
||||
}
|
||||
|
||||
this_element.previous = Some(after_this);
|
||||
this_element.next = after.next;
|
||||
|
||||
after.next = Some(elem);
|
||||
|
||||
this_element_store.set(this_element);
|
||||
after_store.set(after);
|
||||
}
|
||||
|
||||
fn move_before(store: &Store, source: ObjectKey, before_this: ObjectKey) {
|
||||
assert_ne!(source, before_this);
|
||||
|
||||
remove_from_linked_list(store, source);
|
||||
add_before_element(store, source, before_this);
|
||||
}
|
||||
|
||||
fn move_after(store: &Store, source: ObjectKey, after_this: ObjectKey) {
|
||||
assert_ne!(source, after_this);
|
||||
|
||||
remove_from_linked_list(store, source);
|
||||
add_after_element(store, source, after_this);
|
||||
}
|
||||
|
||||
impl Object<'_> {
|
||||
/// Sets the z position of an object. This is not a GBA concept. It causes
|
||||
/// the order of rendering to be different, thus changing whether objects
|
||||
/// are rendered above eachother.
|
||||
///
|
||||
/// Negative z is more towards the outside and positive z is further into
|
||||
/// the screen => an object with a more *negative* z is drawn on top of an
|
||||
/// object with a more *positive* z.
|
||||
pub fn set_z(&mut self, z_index: i32) -> &mut Self {
|
||||
let my_object = &self.store.get_object(self.me);
|
||||
|
||||
let order = z_index.cmp(&my_object.z_index.get());
|
||||
|
||||
match order {
|
||||
core::cmp::Ordering::Equal => {}
|
||||
core::cmp::Ordering::Less => {
|
||||
let mut previous_index = self.me;
|
||||
let mut current_index = self.me;
|
||||
while self.store.get_object(current_index).z_index.get() > z_index {
|
||||
previous_index = current_index;
|
||||
let previous = self.store.get_object(current_index).z_order.get().previous;
|
||||
if let Some(previous) = previous {
|
||||
current_index = previous;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if previous_index != self.me {
|
||||
move_before(self.store, self.me, previous_index);
|
||||
}
|
||||
}
|
||||
core::cmp::Ordering::Greater => {
|
||||
let mut previous_index = self.me;
|
||||
let mut current_index = self.me;
|
||||
while self.store.get_object(current_index).z_index.get() < z_index {
|
||||
previous_index = current_index;
|
||||
let next = self.store.get_object(current_index).z_order.get().next;
|
||||
if let Some(next) = next {
|
||||
current_index = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if previous_index != self.me {
|
||||
move_after(self.store, self.me, previous_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my_object.z_index.set(z_index);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Safety:
|
||||
/// Only have *ONE* of these at a time, do not call any functions that modify the slot map while having this.
|
||||
unsafe fn object(&mut self) -> &mut ObjectUnmanaged {
|
||||
unsafe { &mut *self.store.get_object(self.me).object.get() }
|
||||
}
|
||||
|
||||
/// Safety:
|
||||
/// Don't have a mutable one of these while having one of these, do not call any functions that modify the slot map while having this.
|
||||
unsafe fn object_shared(&self) -> &ObjectUnmanaged {
|
||||
unsafe { &*self.store.get_object(self.me).object.get() }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks whether the object is not marked as hidden. Note that it could be
|
||||
/// off screen or completely transparent and still claimed to be visible.
|
||||
pub fn is_visible(&self) -> bool {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object_shared() }.is_visible()
|
||||
}
|
||||
|
||||
/// Display the sprite in Normal mode.
|
||||
pub fn show(&mut self) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().show() };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Display the sprite in Affine mode.
|
||||
pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().show_affine(affine_mode) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal flip, note that this only has a visible affect in Normal mode.
|
||||
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_hflip(flip) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the vertical flip, note that this only has a visible affect in Normal mode.
|
||||
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_vflip(flip) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the priority of the object relative to the backgrounds priority.
|
||||
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_priority(priority) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Changes the sprite mode to be hidden, can be changed to Normal or Affine
|
||||
/// modes using [`show`][Object::show] and
|
||||
/// [`show_affine`][Object::show_affine] respectively.
|
||||
pub fn hide(&mut self) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().hide() };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the x position of the object.
|
||||
pub fn set_x(&mut self, x: u16) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_x(x) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the y position of the object.
|
||||
pub fn set_y(&mut self, y: u16) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_y(y) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the position of the object.
|
||||
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_position(position) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the affine matrix. This only has an affect in Affine mode.
|
||||
pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_affine_matrix(affine_matrix) };
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the current sprite for the object.
|
||||
pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self {
|
||||
// safety: only have one of these, doesn't modify slotmap
|
||||
unsafe { self.object().set_sprite(sprite) };
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::{display::object::Graphics, include_aseprite};
|
||||
|
||||
use super::*;
|
||||
|
||||
const TEST_SPRITES: &Graphics = include_aseprite!("examples/gfx/tall.aseprite");
|
||||
|
||||
const TEST_SPRITE: &Sprite = &TEST_SPRITES.sprites()[0];
|
||||
|
||||
#[test_case]
|
||||
fn test_always_ordered(gba: &mut crate::Gba) {
|
||||
let managed = gba.display.object.get_managed();
|
||||
|
||||
let sprite = managed.sprite(TEST_SPRITE);
|
||||
|
||||
let mut objects = Vec::new();
|
||||
for _ in 0..200 {
|
||||
let obj = managed.object(sprite.clone());
|
||||
objects.push(obj);
|
||||
}
|
||||
|
||||
for modification_number in 0..10_000 {
|
||||
let index_to_modify = (crate::rng::gen() as usize) % objects.len();
|
||||
let modify_to = crate::rng::gen();
|
||||
objects[index_to_modify].set_z(modify_to);
|
||||
|
||||
assert!(
|
||||
managed.object_store.is_all_ordered_right(),
|
||||
"objects are unordered after {} modifications. Modified {} to {}.",
|
||||
modification_number + 1,
|
||||
index_to_modify,
|
||||
modify_to
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
7
agb/src/display/object/sprites.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod sprite;
|
||||
mod sprite_allocator;
|
||||
|
||||
const BYTES_PER_TILE_4BPP: usize = 32;
|
||||
|
||||
pub use sprite::{include_aseprite, Graphics, Size, Sprite, Tag, TagMap};
|
||||
pub use sprite_allocator::{DynamicSprite, PaletteVram, SpriteLoader, SpriteVram};
|
378
agb/src/display/object/sprites/sprite.rs
Normal file
|
@ -0,0 +1,378 @@
|
|||
use core::{alloc::Layout, slice};
|
||||
|
||||
use crate::display::palette16::Palette16;
|
||||
|
||||
use super::BYTES_PER_TILE_4BPP;
|
||||
|
||||
/// Sprite data. Refers to the palette, pixel data, and the size of the sprite.
|
||||
pub struct Sprite {
|
||||
pub(crate) palette: &'static Palette16,
|
||||
pub(crate) data: &'static [u8],
|
||||
pub(crate) size: Size,
|
||||
}
|
||||
|
||||
impl Sprite {
|
||||
#[doc(hidden)]
|
||||
/// Creates a sprite from it's constituent data, used internally by
|
||||
/// [include_aseprite] and should generally not be used outside it.
|
||||
///
|
||||
/// # Safety
|
||||
/// The data should be aligned to a 2 byte boundary
|
||||
#[must_use]
|
||||
pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self {
|
||||
Self {
|
||||
palette,
|
||||
data,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Gives the size of the sprite
|
||||
pub fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
/// The sizes of sprite supported by the GBA.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Size {
|
||||
// stored as attr0 attr1
|
||||
S8x8 = 0b00_00,
|
||||
S16x16 = 0b00_01,
|
||||
S32x32 = 0b00_10,
|
||||
S64x64 = 0b00_11,
|
||||
|
||||
S16x8 = 0b01_00,
|
||||
S32x8 = 0b01_01,
|
||||
S32x16 = 0b01_10,
|
||||
S64x32 = 0b01_11,
|
||||
|
||||
S8x16 = 0b10_00,
|
||||
S8x32 = 0b10_01,
|
||||
S16x32 = 0b10_10,
|
||||
S32x64 = 0b10_11,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! align_bytes {
|
||||
($align_ty:ty, $data:literal) => {{
|
||||
#[repr(C)] // guarantee 'bytes' comes after '_align'
|
||||
struct AlignedAs<Align, Bytes: ?Sized> {
|
||||
pub _align: [Align; 0],
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
|
||||
const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs {
|
||||
_align: [],
|
||||
bytes: *$data,
|
||||
};
|
||||
|
||||
&ALIGNED.bytes
|
||||
}};
|
||||
}
|
||||
|
||||
/// Includes sprites found in the referenced aseprite files. Can include
|
||||
/// multiple at once and optimises palettes of all included in the single call
|
||||
/// together. See [Size] for supported sizes. Returns a reference to [Graphics].
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #![no_std]
|
||||
/// # #![no_main]
|
||||
/// # use agb::{display::object::Graphics, include_aseprite};
|
||||
/// const GRAPHICS: &Graphics = include_aseprite!(
|
||||
/// "examples/gfx/boss.aseprite",
|
||||
/// "examples/gfx/objects.aseprite"
|
||||
/// );
|
||||
/// ```
|
||||
/// The tags from the aseprite file are included so you can refer to sprites by
|
||||
/// name in code. You should ensure tags are unique as this is not enforced by
|
||||
/// aseprite.
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! include_aseprite {
|
||||
($($aseprite_path: expr),*) => {{
|
||||
#[allow(unused_imports)]
|
||||
use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics};
|
||||
use $crate::display::palette16::Palette16;
|
||||
use $crate::align_bytes;
|
||||
|
||||
$crate::include_aseprite_inner!($($aseprite_path),*);
|
||||
|
||||
&Graphics::new(SPRITES, TAGS)
|
||||
}};
|
||||
}
|
||||
|
||||
pub use include_aseprite;
|
||||
|
||||
/// Stores sprite and tag data returned by [include_aseprite].
|
||||
pub struct Graphics {
|
||||
sprites: &'static [Sprite],
|
||||
tag_map: &'static TagMap,
|
||||
}
|
||||
|
||||
impl Graphics {
|
||||
#[doc(hidden)]
|
||||
/// Creates graphics data from sprite data and a tag_map. This is used
|
||||
/// internally by [include_aseprite] and would be otherwise difficult to
|
||||
/// use.
|
||||
#[must_use]
|
||||
pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self {
|
||||
Self { sprites, tag_map }
|
||||
}
|
||||
#[must_use]
|
||||
/// Gets the tag map from the aseprite files. This allows reference to
|
||||
/// sprite sequences by name.
|
||||
pub const fn tags(&self) -> &TagMap {
|
||||
self.tag_map
|
||||
}
|
||||
/// Gets a big list of the sprites themselves. Using tags is often easier.
|
||||
#[must_use]
|
||||
pub const fn sprites(&self) -> &[Sprite] {
|
||||
self.sprites
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores aseprite tags. Can be used to refer to animation sequences by name.
|
||||
/// ```rust,no_run
|
||||
/// # #![no_std]
|
||||
/// # #![no_main]
|
||||
/// # use agb::{display::object::{Graphics, Tag}, include_aseprite};
|
||||
/// const GRAPHICS: &Graphics = include_aseprite!(
|
||||
/// "examples/gfx/boss.aseprite",
|
||||
/// "examples/gfx/objects.aseprite"
|
||||
/// );
|
||||
///
|
||||
/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk");
|
||||
/// ```
|
||||
/// This being the whole animation associated with the walk sequence of the emu.
|
||||
/// See [Tag] for details on how to use this.
|
||||
pub struct TagMap {
|
||||
tags: &'static [(&'static str, Tag)],
|
||||
}
|
||||
|
||||
const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool {
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
while i < a.len() {
|
||||
if a[i] != b[i] {
|
||||
return false;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
impl TagMap {
|
||||
#[doc(hidden)]
|
||||
/// Creates a new tag map from (name, Tag) pairs. Used internally by
|
||||
/// [include_aseprite] and should not really be used outside of it.
|
||||
#[must_use]
|
||||
pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap {
|
||||
Self { tags }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Attempts to get a tag. Generally should not be used.
|
||||
#[must_use]
|
||||
pub const fn try_get(&'static self, tag: &str) -> Option<&'static Tag> {
|
||||
let mut i = 0;
|
||||
while i < self.tags.len() {
|
||||
let s = self.tags[i].0;
|
||||
if const_byte_compare(s.as_bytes(), tag.as_bytes()) {
|
||||
return Some(&self.tags[i].1);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets a tag associated with the name. A tag in aseprite refers to a
|
||||
/// sequence of sprites with some metadata for how to animate it. You should
|
||||
/// call this in a constant context so it is evaluated at compile time. It
|
||||
/// is inefficient to call this elsewhere.
|
||||
/// ```rust,no_run
|
||||
/// # #![no_std]
|
||||
/// # #![no_main]
|
||||
/// # use agb::{display::object::{Graphics, Tag}, include_aseprite};
|
||||
/// const GRAPHICS: &Graphics = include_aseprite!(
|
||||
/// "examples/gfx/boss.aseprite",
|
||||
/// "examples/gfx/objects.aseprite"
|
||||
/// );
|
||||
///
|
||||
/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk");
|
||||
/// ```
|
||||
///
|
||||
/// See [Tag] for more details.
|
||||
#[must_use]
|
||||
pub const fn get(&'static self, tag: &str) -> &'static Tag {
|
||||
let t = self.try_get(tag);
|
||||
match t {
|
||||
Some(t) => t,
|
||||
None => panic!("The requested tag does not exist"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes an iterator over all the tags in the map. Not generally useful.
|
||||
pub fn values(&self) -> impl Iterator<Item = &'static Tag> {
|
||||
self.tags.iter().map(|x| &x.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Direction {
|
||||
Forward,
|
||||
Backward,
|
||||
PingPong,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
const fn from_usize(a: usize) -> Self {
|
||||
match a {
|
||||
0 => Direction::Forward,
|
||||
1 => Direction::Backward,
|
||||
2 => Direction::PingPong,
|
||||
_ => panic!("Invalid direction, this is a bug in image converter or agb"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of sprites from aseprite.
|
||||
pub struct Tag {
|
||||
sprites: *const Sprite,
|
||||
len: usize,
|
||||
direction: Direction,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
/// The individual sprites that make up the animation themselves.
|
||||
#[must_use]
|
||||
pub fn sprites(&self) -> &'static [Sprite] {
|
||||
unsafe { slice::from_raw_parts(self.sprites, self.len) }
|
||||
}
|
||||
|
||||
/// A single sprite referred to by index in the animation sequence.
|
||||
#[must_use]
|
||||
pub const fn sprite(&self, idx: usize) -> &'static Sprite {
|
||||
if idx >= self.len {
|
||||
panic!("out of bounds access to sprite");
|
||||
}
|
||||
unsafe { &*self.sprites.add(idx) }
|
||||
}
|
||||
|
||||
/// A sprite that follows the animation sequence. For instance, in aseprite
|
||||
/// tags can be specified to animate:
|
||||
/// * Forward
|
||||
/// * Backward
|
||||
/// * Ping pong
|
||||
///
|
||||
/// This takes the animation type in account and returns the correct sprite
|
||||
/// following these requirements.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn animation_sprite(&self, idx: usize) -> &'static Sprite {
|
||||
let len_sub_1 = self.len - 1;
|
||||
match self.direction {
|
||||
Direction::Forward => self.sprite(idx % self.len),
|
||||
Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)),
|
||||
Direction::PingPong => self.sprite(
|
||||
(((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize)
|
||||
.unsigned_abs(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Creates a new sprite from it's constituent parts. Used internally by
|
||||
/// [include_aseprite] and should generally not be used elsewhere.
|
||||
#[must_use]
|
||||
pub const fn new(sprites: &'static [Sprite], from: usize, to: usize, direction: usize) -> Self {
|
||||
assert!(from <= to);
|
||||
assert!(to < sprites.len());
|
||||
Self {
|
||||
sprites: &sprites[from] as *const Sprite,
|
||||
len: to - from + 1,
|
||||
direction: Direction::from_usize(direction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub(crate) const fn number_of_tiles(self) -> usize {
|
||||
match self {
|
||||
Size::S8x8 => 1,
|
||||
Size::S16x16 => 4,
|
||||
Size::S32x32 => 16,
|
||||
Size::S64x64 => 64,
|
||||
Size::S16x8 => 2,
|
||||
Size::S32x8 => 4,
|
||||
Size::S32x16 => 8,
|
||||
Size::S64x32 => 32,
|
||||
Size::S8x16 => 2,
|
||||
Size::S8x32 => 4,
|
||||
Size::S16x32 => 8,
|
||||
Size::S32x64 => 32,
|
||||
}
|
||||
}
|
||||
pub(crate) const fn shape_size(self) -> (u16, u16) {
|
||||
(self as u16 >> 2, self as u16 & 0b11)
|
||||
}
|
||||
|
||||
pub(crate) fn layout(self) -> Layout {
|
||||
Layout::from_size_align(self.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Creates a size from width and height in pixels, panics if the width and
|
||||
/// height is not representable by GBA sprites.
|
||||
pub const fn from_width_height(width: usize, height: usize) -> Self {
|
||||
match (width, height) {
|
||||
(8, 8) => Size::S8x8,
|
||||
(16, 16) => Size::S16x16,
|
||||
(32, 32) => Size::S32x32,
|
||||
(64, 64) => Size::S64x64,
|
||||
(16, 8) => Size::S16x8,
|
||||
(32, 8) => Size::S32x8,
|
||||
(32, 16) => Size::S32x16,
|
||||
(64, 32) => Size::S64x32,
|
||||
(8, 16) => Size::S8x16,
|
||||
(8, 32) => Size::S8x32,
|
||||
(16, 32) => Size::S16x32,
|
||||
(32, 64) => Size::S32x64,
|
||||
(_, _) => panic!("Bad width and height!"),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns the width and height of the size in pixels.
|
||||
pub const fn to_width_height(self) -> (usize, usize) {
|
||||
match self {
|
||||
Size::S8x8 => (8, 8),
|
||||
Size::S16x16 => (16, 16),
|
||||
Size::S32x32 => (32, 32),
|
||||
Size::S64x64 => (64, 64),
|
||||
Size::S16x8 => (16, 8),
|
||||
Size::S32x8 => (32, 8),
|
||||
Size::S32x16 => (32, 16),
|
||||
Size::S64x32 => (64, 32),
|
||||
Size::S8x16 => (8, 16),
|
||||
Size::S8x32 => (8, 32),
|
||||
Size::S16x32 => (16, 32),
|
||||
Size::S32x64 => (32, 64),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns the width and height of the size in pixels.
|
||||
pub const fn to_tiles_width_height(self) -> (usize, usize) {
|
||||
let wh = self.to_width_height();
|
||||
(wh.0 / 8, wh.1 / 8)
|
||||
}
|
||||
}
|
356
agb/src/display/object/sprites/sprite_allocator.rs
Normal file
|
@ -0,0 +1,356 @@
|
|||
use core::{alloc::Allocator, ptr::NonNull};
|
||||
|
||||
use alloc::{
|
||||
alloc::Global,
|
||||
boxed::Box,
|
||||
rc::{Rc, Weak},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
|
||||
display::palette16::Palette16,
|
||||
hash_map::HashMap,
|
||||
};
|
||||
|
||||
use super::{
|
||||
sprite::{Size, Sprite},
|
||||
BYTES_PER_TILE_4BPP,
|
||||
};
|
||||
|
||||
const PALETTE_SPRITE: usize = 0x0500_0200;
|
||||
const TILE_SPRITE: usize = 0x06010000;
|
||||
|
||||
static SPRITE_ALLOCATOR: BlockAllocator = unsafe {
|
||||
BlockAllocator::new(StartEnd {
|
||||
start: || TILE_SPRITE,
|
||||
end: || TILE_SPRITE + 1024 * 8 * 4,
|
||||
})
|
||||
};
|
||||
|
||||
static PALETTE_ALLOCATOR: BlockAllocator = unsafe {
|
||||
BlockAllocator::new(StartEnd {
|
||||
start: || PALETTE_SPRITE,
|
||||
end: || PALETTE_SPRITE + 0x200,
|
||||
})
|
||||
};
|
||||
|
||||
/// The Sprite Id is a thin wrapper around the pointer to the sprite in
|
||||
/// rom and is therefore a unique identifier to a sprite
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct SpriteId(usize);
|
||||
|
||||
impl SpriteId {
|
||||
fn from_static_sprite(sprite: &'static Sprite) -> SpriteId {
|
||||
SpriteId(sprite as *const _ as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// The palette id is a thin wrapper around the pointer to the palette in rom
|
||||
/// and is therefore a unique reference to a palette
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct PaletteId(usize);
|
||||
|
||||
impl PaletteId {
|
||||
fn from_static_palette(palette: &'static Palette16) -> PaletteId {
|
||||
PaletteId(palette as *const _ as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// This holds loading of static sprites and palettes.
|
||||
pub struct SpriteLoader {
|
||||
static_palette_map: HashMap<PaletteId, Weak<PaletteVramData>>,
|
||||
static_sprite_map: HashMap<SpriteId, Weak<SpriteVramData>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Location(usize);
|
||||
|
||||
impl Location {
|
||||
fn from_sprite_ptr(d: NonNull<u8>) -> Self {
|
||||
Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP)
|
||||
}
|
||||
fn from_palette_ptr(d: NonNull<u8>) -> Self {
|
||||
Self((d.as_ptr() as usize - PALETTE_SPRITE) / Palette16::layout().size())
|
||||
}
|
||||
fn as_palette_ptr(self) -> *mut u8 {
|
||||
(self.0 * Palette16::layout().size() + PALETTE_SPRITE) as *mut u8
|
||||
}
|
||||
fn as_sprite_ptr(self) -> *mut u8 {
|
||||
(self.0 * BYTES_PER_TILE_4BPP + TILE_SPRITE) as *mut u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PaletteVramData {
|
||||
location: Location,
|
||||
}
|
||||
|
||||
impl Drop for PaletteVramData {
|
||||
fn drop(&mut self) {
|
||||
unsafe { PALETTE_ALLOCATOR.dealloc(self.location.as_palette_ptr(), Palette16::layout()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A palette in vram, this is reference counted so it is cheap to Clone.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaletteVram {
|
||||
data: Rc<PaletteVramData>,
|
||||
}
|
||||
|
||||
impl PaletteVram {
|
||||
/// Attempts to allocate a new palette in sprite vram
|
||||
pub fn new(palette: &Palette16) -> Result<PaletteVram, LoaderError> {
|
||||
let allocated = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout()) }
|
||||
.ok_or(LoaderError::PaletteFull)?;
|
||||
|
||||
unsafe {
|
||||
allocated
|
||||
.as_ptr()
|
||||
.cast::<u16>()
|
||||
.copy_from_nonoverlapping(palette.colours.as_ptr(), palette.colours.len());
|
||||
}
|
||||
|
||||
Ok(PaletteVram {
|
||||
data: Rc::new(PaletteVramData {
|
||||
location: Location::from_palette_ptr(allocated),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SpriteVramData {
|
||||
location: Location,
|
||||
size: Size,
|
||||
palette: PaletteVram,
|
||||
}
|
||||
|
||||
impl Drop for SpriteVramData {
|
||||
fn drop(&mut self) {
|
||||
unsafe { SPRITE_ALLOCATOR.dealloc(self.location.as_sprite_ptr(), self.size.layout()) }
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum LoaderError {
|
||||
SpriteFull,
|
||||
PaletteFull,
|
||||
}
|
||||
|
||||
/// A sprite that is currently loaded into vram.
|
||||
///
|
||||
/// This is referenced counted such that clones of this are cheap and can be
|
||||
/// reused between objects. When nothing references the sprite it gets
|
||||
/// deallocated from vram.
|
||||
///
|
||||
/// You can create one of these either via the [DynamicSprite] interface, which
|
||||
/// allows you to generate sprites at run time, or via a [SpriteLoader] (or
|
||||
/// [OamManaged][super::super::OamManaged]).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpriteVram {
|
||||
data: Rc<SpriteVramData>,
|
||||
}
|
||||
|
||||
impl SpriteVram {
|
||||
fn new(data: &[u8], size: Size, palette: PaletteVram) -> Result<SpriteVram, LoaderError> {
|
||||
let allocated =
|
||||
unsafe { SPRITE_ALLOCATOR.alloc(size.layout()) }.ok_or(LoaderError::SpriteFull)?;
|
||||
unsafe {
|
||||
allocated
|
||||
.as_ptr()
|
||||
.copy_from_nonoverlapping(data.as_ptr(), data.len());
|
||||
}
|
||||
Ok(SpriteVram {
|
||||
data: Rc::new(SpriteVramData {
|
||||
location: Location::from_sprite_ptr(allocated),
|
||||
size,
|
||||
palette,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn location(&self) -> u16 {
|
||||
self.data.location.0 as u16
|
||||
}
|
||||
|
||||
pub(crate) fn size(&self) -> Size {
|
||||
self.data.size
|
||||
}
|
||||
|
||||
pub(crate) fn palette_location(&self) -> u16 {
|
||||
self.data.palette.data.location.0 as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl SpriteLoader {
|
||||
fn create_sprite_no_insert(
|
||||
palette_map: &mut HashMap<PaletteId, Weak<PaletteVramData>>,
|
||||
sprite: &'static Sprite,
|
||||
) -> Result<(Weak<SpriteVramData>, SpriteVram), LoaderError> {
|
||||
let palette = Self::try_get_vram_palette_asoc(palette_map, sprite.palette)?;
|
||||
|
||||
let sprite = SpriteVram::new(sprite.data, sprite.size, palette)?;
|
||||
Ok((Rc::downgrade(&sprite.data), sprite))
|
||||
}
|
||||
|
||||
fn try_get_vram_palette_asoc(
|
||||
palette_map: &mut HashMap<PaletteId, Weak<PaletteVramData>>,
|
||||
palette: &'static Palette16,
|
||||
) -> Result<PaletteVram, LoaderError> {
|
||||
let id = PaletteId::from_static_palette(palette);
|
||||
Ok(match palette_map.entry(id) {
|
||||
crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() {
|
||||
Some(data) => PaletteVram { data },
|
||||
None => {
|
||||
let pv = PaletteVram::new(palette)?;
|
||||
entry.insert(Rc::downgrade(&pv.data));
|
||||
pv
|
||||
}
|
||||
},
|
||||
crate::hash_map::Entry::Vacant(entry) => {
|
||||
let pv = PaletteVram::new(palette)?;
|
||||
entry.insert(Rc::downgrade(&pv.data));
|
||||
pv
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to get a sprite
|
||||
pub fn try_get_vram_sprite(
|
||||
&mut self,
|
||||
sprite: &'static Sprite,
|
||||
) -> Result<SpriteVram, LoaderError> {
|
||||
// check if we already have the sprite in vram
|
||||
|
||||
let id = SpriteId::from_static_sprite(sprite);
|
||||
|
||||
Ok(match self.static_sprite_map.entry(id) {
|
||||
crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() {
|
||||
Some(data) => SpriteVram { data },
|
||||
None => {
|
||||
let (weak, vram) =
|
||||
Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?;
|
||||
entry.insert(weak);
|
||||
vram
|
||||
}
|
||||
},
|
||||
crate::hash_map::Entry::Vacant(entry) => {
|
||||
let (weak, vram) =
|
||||
Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?;
|
||||
entry.insert(weak);
|
||||
vram
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to allocate a static palette
|
||||
pub fn try_get_vram_palette(
|
||||
&mut self,
|
||||
palette: &'static Palette16,
|
||||
) -> Result<PaletteVram, LoaderError> {
|
||||
Self::try_get_vram_palette_asoc(&mut self.static_palette_map, palette)
|
||||
}
|
||||
|
||||
/// Allocates a sprite to vram, panics if it cannot fit.
|
||||
pub fn get_vram_sprite(&mut self, sprite: &'static Sprite) -> SpriteVram {
|
||||
self.try_get_vram_sprite(sprite)
|
||||
.expect("cannot create sprite")
|
||||
}
|
||||
|
||||
/// Allocates a palette to vram, panics if it cannot fit.
|
||||
pub fn get_vram_palette(&mut self, palette: &'static Palette16) -> PaletteVram {
|
||||
self.try_get_vram_palette(palette)
|
||||
.expect("cannot create sprite")
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
static_palette_map: HashMap::new(),
|
||||
static_sprite_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove internal references to sprites that no longer exist in vram. If
|
||||
/// you neglect calling this, memory will leak over time in relation to the
|
||||
/// total number of different sprites used. It will not leak vram.
|
||||
pub fn garbage_collect(&mut self) {
|
||||
self.static_sprite_map
|
||||
.retain(|_, v| Weak::strong_count(v) != 0);
|
||||
self.static_palette_map
|
||||
.retain(|_, v| Weak::strong_count(v) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SpriteLoader {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sprite data that can be used to create sprites in vram.
|
||||
pub struct DynamicSprite<A: Allocator = Global> {
|
||||
data: Box<[u8], A>,
|
||||
size: Size,
|
||||
}
|
||||
|
||||
impl DynamicSprite {
|
||||
#[must_use]
|
||||
/// Creates a new dynamic sprite.
|
||||
pub fn new(size: Size) -> Self {
|
||||
Self::new_in(size, Global)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Allocator> DynamicSprite<A> {
|
||||
#[must_use]
|
||||
/// Creates a new dynamic sprite of a given size in a given allocator.
|
||||
pub fn new_in(size: Size, allocator: A) -> Self {
|
||||
let num_bytes = size.number_of_tiles() * BYTES_PER_TILE_4BPP;
|
||||
let mut data = Vec::with_capacity_in(num_bytes, allocator);
|
||||
|
||||
data.resize(num_bytes, 0);
|
||||
|
||||
let data = data.into_boxed_slice();
|
||||
|
||||
DynamicSprite { data, size }
|
||||
}
|
||||
|
||||
/// Set the pixel of a sprite to a given paletted pixel. Panics if the
|
||||
/// coordinate is out of range of the sprite or if the paletted pixel is
|
||||
/// greater than 4 bits.
|
||||
pub fn set_pixel(&mut self, x: usize, y: usize, paletted_pixel: usize) {
|
||||
assert!(paletted_pixel < 0x10);
|
||||
|
||||
let (sprite_pixel_x, sprite_pixel_y) = self.size.to_width_height();
|
||||
assert!(x < sprite_pixel_x, "x too big for sprite size");
|
||||
assert!(y < sprite_pixel_y, "y too big for sprite size");
|
||||
|
||||
let (sprite_tile_x, _) = self.size.to_tiles_width_height();
|
||||
|
||||
let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8);
|
||||
|
||||
let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x;
|
||||
|
||||
let byte_to_modify_in_tile = x / 2 + y * 4;
|
||||
let byte_to_modify = tile_number_to_modify * BYTES_PER_TILE_4BPP + byte_to_modify_in_tile;
|
||||
let mut byte = self.data[byte_to_modify];
|
||||
let parity = (x & 0b1) * 4;
|
||||
|
||||
byte = (byte & !(0b1111 << parity)) | ((paletted_pixel as u8) << parity);
|
||||
self.data[byte_to_modify] = byte;
|
||||
}
|
||||
|
||||
/// Tries to copy the sprite to vram to be used to set object sprites.
|
||||
pub fn try_vram(&self, palette: PaletteVram) -> Result<SpriteVram, LoaderError> {
|
||||
SpriteVram::new(&self.data, self.size, palette)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Tries to copy the sprite to vram to be used to set object sprites.
|
||||
/// Panics if it cannot be allocated.
|
||||
pub fn to_vram(&self, palette: PaletteVram) -> SpriteVram {
|
||||
self.try_vram(palette).expect("cannot create sprite")
|
||||
}
|
||||
}
|
5
agb/src/display/object/unmanaged.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod attributes;
|
||||
mod object;
|
||||
|
||||
pub use attributes::AffineMode;
|
||||
pub use object::{OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged};
|
210
agb/src/display/object/unmanaged/attributes.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
use modular_bitfield::BitfieldSpecifier;
|
||||
|
||||
use crate::display::Priority;
|
||||
|
||||
use self::attributes::{
|
||||
ObjectAttribute0, ObjectAttribute1Affine, ObjectAttribute1Standard, ObjectAttribute2,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub struct Attributes {
|
||||
a0: ObjectAttribute0,
|
||||
a1s: ObjectAttribute1Standard,
|
||||
a1a: ObjectAttribute1Affine,
|
||||
a2: ObjectAttribute2,
|
||||
}
|
||||
|
||||
impl Default for Attributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
a0: ObjectAttribute0::from_bytes([0, 0b10]),
|
||||
a1s: Default::default(),
|
||||
a1a: Default::default(),
|
||||
a2: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
/// The affine mode
|
||||
pub enum AffineMode {
|
||||
/// Normal affine, this is where the area of the affine is equal to the sprite size
|
||||
Affine = 1,
|
||||
/// Double affine, this is where the area of the affine is double that of the sprite
|
||||
AffineDouble = 3,
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
pub fn write(self, ptr: *mut u16) {
|
||||
let mode = self.a0.object_mode();
|
||||
unsafe {
|
||||
let attrs = core::mem::transmute::<_, [u16; 3]>(match mode {
|
||||
ObjectMode::Normal => [
|
||||
self.a0.into_bytes(),
|
||||
self.a1s.into_bytes(),
|
||||
self.a2.into_bytes(),
|
||||
],
|
||||
_ => [
|
||||
self.a0.into_bytes(),
|
||||
self.a1a.into_bytes(),
|
||||
self.a2.into_bytes(),
|
||||
],
|
||||
});
|
||||
|
||||
ptr.add(0).write_volatile(attrs[0]);
|
||||
ptr.add(1).write_volatile(attrs[1]);
|
||||
ptr.add(2).write_volatile(attrs[2]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_visible(self) -> bool {
|
||||
self.a0.object_mode() != ObjectMode::Disabled
|
||||
}
|
||||
|
||||
pub fn show(&mut self) -> &mut Self {
|
||||
self.a0.set_object_mode(ObjectMode::Normal);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self {
|
||||
self.a0.set_object_mode(match affine_mode {
|
||||
AffineMode::Affine => ObjectMode::Affine,
|
||||
AffineMode::AffineDouble => ObjectMode::AffineDouble,
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
|
||||
self.a1s.set_horizontal_flip(flip);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
|
||||
self.a1s.set_vertical_flip(flip);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_x(&mut self, x: u16) -> &mut Self {
|
||||
self.a1a.set_x(x.rem_euclid(1 << 9));
|
||||
self.a1s.set_x(x.rem_euclid(1 << 9));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
|
||||
self.a2.set_priority(priority);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hide(&mut self) -> &mut Self {
|
||||
self.a0.set_object_mode(ObjectMode::Disabled);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_y(&mut self, y: u16) -> &mut Self {
|
||||
self.a0.set_y(y as u8);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_palette(&mut self, palette_id: u16) -> &mut Self {
|
||||
self.a2.set_palette_bank(palette_id as u8);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_affine_matrix(&mut self, affine_matrix_id: u16) -> &mut Self {
|
||||
self.a1a.set_affine_index(affine_matrix_id as u8);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_sprite(&mut self, sprite_id: u16, shape: u16, size: u16) -> &mut Self {
|
||||
self.a2.set_tile_index(sprite_id);
|
||||
self.a1a.set_size(size as u8);
|
||||
self.a1s.set_size(size as u8);
|
||||
self.a0.set_shape(shape as u8);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum ObjectMode {
|
||||
Normal,
|
||||
Affine,
|
||||
Disabled,
|
||||
AffineDouble,
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[bits = 2]
|
||||
enum GraphicsMode {
|
||||
Normal,
|
||||
AlphaBlending,
|
||||
Window,
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum ColourMode {
|
||||
Four,
|
||||
Eight,
|
||||
}
|
||||
|
||||
// this mod is not public, so the internal parts don't need documenting.
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::all)]
|
||||
#[allow(clippy::map_unwrap_or)]
|
||||
mod attributes {
|
||||
use modular_bitfield::{
|
||||
bitfield,
|
||||
specifiers::{B10, B2, B3, B4, B5, B8, B9},
|
||||
};
|
||||
|
||||
use crate::display::Priority;
|
||||
|
||||
use super::*;
|
||||
#[bitfield]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
pub(super) struct ObjectAttribute0 {
|
||||
pub y: B8,
|
||||
pub object_mode: ObjectMode,
|
||||
pub graphics_mode: GraphicsMode,
|
||||
pub mosaic: bool,
|
||||
pub colour_mode: ColourMode,
|
||||
pub shape: B2,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
pub(super) struct ObjectAttribute1Standard {
|
||||
pub x: B9,
|
||||
#[skip]
|
||||
__: B3,
|
||||
pub horizontal_flip: bool,
|
||||
pub vertical_flip: bool,
|
||||
pub size: B2,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
pub(super) struct ObjectAttribute1Affine {
|
||||
pub x: B9,
|
||||
pub affine_index: B5,
|
||||
pub size: B2,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
pub(super) struct ObjectAttribute2 {
|
||||
pub tile_index: B10,
|
||||
pub priority: Priority,
|
||||
pub palette_bank: B4,
|
||||
}
|
||||
}
|
375
agb/src/display/object/unmanaged/object.rs
Normal file
|
@ -0,0 +1,375 @@
|
|||
use core::{cell::UnsafeCell, marker::PhantomData};
|
||||
|
||||
use agb_fixnum::Vector2D;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::display::{
|
||||
object::{
|
||||
affine::AffineMatrixVram, sprites::SpriteVram, AffineMatrixInstance,
|
||||
OBJECT_ATTRIBUTE_MEMORY,
|
||||
},
|
||||
Priority,
|
||||
};
|
||||
|
||||
use super::attributes::{AffineMode, Attributes};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OamFrameModifyables {
|
||||
this_frame_sprites: Vec<SpriteVram>,
|
||||
frame: u32,
|
||||
affine_matrix_count: u32,
|
||||
previous_index: usize,
|
||||
}
|
||||
|
||||
/// This handles the unmanaged oam system which gives more control to the OAM slots.
|
||||
/// This is utilised by calling the iter function and writing objects to those slots.
|
||||
pub struct OamUnmanaged<'gba> {
|
||||
phantom: PhantomData<&'gba ()>,
|
||||
frame_data: UnsafeCell<OamFrameModifyables>,
|
||||
previous_frame_sprites: Vec<SpriteVram>,
|
||||
}
|
||||
|
||||
/// The iterator over the OAM slots. Dropping this will finalise the frame. To
|
||||
/// use, iterate over and write to each slot.
|
||||
///
|
||||
/// For example, it could look like this:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![no_main]
|
||||
/// # #![no_std]
|
||||
/// use agb::display::object::{OamIterator, ObjectUnmanaged};
|
||||
///
|
||||
/// fn write_to_oam(oam_iterator: OamIterator, objects: &[ObjectUnmanaged]) {
|
||||
/// for (object, slot) in objects.iter().zip(oam_iterator) {
|
||||
/// slot.set(&object);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Pitfalls
|
||||
/// You *must* use each OamSlot you obtain, this can be an issue if instead of
|
||||
/// the above you write
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![no_main]
|
||||
/// # #![no_std]
|
||||
/// use agb::display::object::{OamIterator, ObjectUnmanaged};
|
||||
///
|
||||
/// fn write_to_oam(oam_iterator: OamIterator, objects: &[ObjectUnmanaged]) {
|
||||
/// for (slot, object) in oam_iterator.zip(objects.iter()) {
|
||||
/// slot.set(&object);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This will panic if called because when you run out of objects the zip will
|
||||
/// have already grabbed the next OamSlot before realising there are no more
|
||||
/// objects.
|
||||
|
||||
pub struct OamIterator<'oam> {
|
||||
index: usize,
|
||||
frame_data: &'oam UnsafeCell<OamFrameModifyables>,
|
||||
}
|
||||
|
||||
/// A slot in Oam that you can write to. Note that you must call [OamSlot::set]
|
||||
/// or else it is a bug and will panic when dropped.
|
||||
///
|
||||
/// See [`OamIterator`] for potential pitfalls.
|
||||
pub struct OamSlot<'oam> {
|
||||
slot: usize,
|
||||
frame_data: &'oam UnsafeCell<OamFrameModifyables>,
|
||||
}
|
||||
|
||||
impl Drop for OamSlot<'_> {
|
||||
#[track_caller]
|
||||
fn drop(&mut self) {
|
||||
panic!("Dropping an OamSlot is a bug in your code. Use the slot by calling set (this consumes the slot) or don't obtain one. See documentation for notes on potential pitfalls.")
|
||||
}
|
||||
}
|
||||
|
||||
impl OamSlot<'_> {
|
||||
/// Set the slot in OAM to contain the sprite given.
|
||||
#[inline(always)]
|
||||
pub fn set(self, object: &ObjectUnmanaged) {
|
||||
self.set_inner(object);
|
||||
|
||||
// don't call the drop implementation.
|
||||
// okay as none of the fields we have have drop implementations.
|
||||
core::mem::forget(self);
|
||||
}
|
||||
|
||||
/// By writing these as two separate functions, one inlined and one not, the
|
||||
/// compiler doesn't have to copy around the slot structure while still
|
||||
/// keeping move semantics. This is slightly faster in benchmarks.
|
||||
#[inline(never)]
|
||||
fn set_inner(&self, object: &ObjectUnmanaged) {
|
||||
let mut attributes = object.attributes;
|
||||
// SAFETY: This function is not reentrant and we currently hold a mutable borrow of the [UnmanagedOAM].
|
||||
let frame_data = unsafe { &mut *self.frame_data.get() };
|
||||
|
||||
if let Some(affine_matrix) = &object.affine_matrix {
|
||||
Self::handle_affine(&mut attributes, frame_data, affine_matrix);
|
||||
}
|
||||
attributes.write(unsafe { (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(self.slot * 4) });
|
||||
|
||||
frame_data.this_frame_sprites.push(object.sprite.clone());
|
||||
}
|
||||
|
||||
fn handle_affine(
|
||||
attributes: &mut Attributes,
|
||||
frame_data: &mut OamFrameModifyables,
|
||||
affine_matrix: &AffineMatrixVram,
|
||||
) {
|
||||
if affine_matrix.frame_count() != frame_data.frame {
|
||||
affine_matrix.set_frame_count(frame_data.frame);
|
||||
assert!(
|
||||
frame_data.affine_matrix_count <= 32,
|
||||
"too many affine matricies in one frame"
|
||||
);
|
||||
affine_matrix.set_location(frame_data.affine_matrix_count);
|
||||
frame_data.affine_matrix_count += 1;
|
||||
affine_matrix.write_to_location(OBJECT_ATTRIBUTE_MEMORY);
|
||||
}
|
||||
|
||||
attributes.set_affine_matrix(affine_matrix.location() as u16);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'oam> Iterator for OamIterator<'oam> {
|
||||
type Item = OamSlot<'oam>;
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let idx = self.index;
|
||||
if idx == 128 {
|
||||
None
|
||||
} else {
|
||||
self.index += 1;
|
||||
Some(OamSlot {
|
||||
slot: idx,
|
||||
frame_data: self.frame_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OamIterator<'_> {
|
||||
fn drop(&mut self) {
|
||||
let number_writen = self.index;
|
||||
let last_frame_written = unsafe { &mut (*self.frame_data.get()).previous_index };
|
||||
|
||||
for idx in number_writen..*last_frame_written {
|
||||
unsafe {
|
||||
let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(idx * 4);
|
||||
ptr.write_volatile(0b10 << 8);
|
||||
}
|
||||
}
|
||||
*last_frame_written = number_writen;
|
||||
}
|
||||
}
|
||||
|
||||
impl OamUnmanaged<'_> {
|
||||
/// Returns the OamSlot iterator for this frame.
|
||||
pub fn iter(&mut self) -> OamIterator<'_> {
|
||||
let frame_data = self.frame_data.get_mut();
|
||||
frame_data.frame = frame_data.frame.wrapping_add(1);
|
||||
frame_data.affine_matrix_count = 0;
|
||||
|
||||
// We drain the previous frame sprites here to reuse the Vecs allocation and remove the now unused sprites.
|
||||
// Any sprites currently being shown will now be put in the new Vec.
|
||||
self.previous_frame_sprites.clear();
|
||||
core::mem::swap(
|
||||
&mut frame_data.this_frame_sprites,
|
||||
&mut self.previous_frame_sprites,
|
||||
);
|
||||
|
||||
OamIterator {
|
||||
index: 0,
|
||||
frame_data: &self.frame_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
frame_data: UnsafeCell::new(OamFrameModifyables {
|
||||
this_frame_sprites: Vec::new(),
|
||||
frame: 0,
|
||||
affine_matrix_count: 0,
|
||||
previous_index: 0,
|
||||
}),
|
||||
phantom: PhantomData,
|
||||
previous_frame_sprites: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// An object to be used by the [`OamUnmanaged`] system. Changes made here are
|
||||
/// reflected when set to an OamSlot using [`OamSlot::set`].
|
||||
pub struct ObjectUnmanaged {
|
||||
attributes: Attributes,
|
||||
sprite: SpriteVram,
|
||||
affine_matrix: Option<AffineMatrixVram>,
|
||||
}
|
||||
|
||||
impl ObjectUnmanaged {
|
||||
#[must_use]
|
||||
/// Creates an unmanaged object from a sprite in vram.
|
||||
pub fn new(sprite: SpriteVram) -> Self {
|
||||
let sprite_location = sprite.location();
|
||||
let palette_location = sprite.palette_location();
|
||||
let (shape, size) = sprite.size().shape_size();
|
||||
|
||||
let mut sprite = Self {
|
||||
attributes: Attributes::default(),
|
||||
sprite,
|
||||
affine_matrix: None,
|
||||
};
|
||||
|
||||
sprite.attributes.set_sprite(sprite_location, shape, size);
|
||||
sprite.attributes.set_palette(palette_location);
|
||||
|
||||
sprite
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Checks whether the object is not marked as hidden. Note that it could be
|
||||
/// off screen or completely transparent and still claimed to be visible.
|
||||
pub fn is_visible(&self) -> bool {
|
||||
self.attributes.is_visible()
|
||||
}
|
||||
|
||||
/// Display the sprite in Normal mode.
|
||||
pub fn show(&mut self) -> &mut Self {
|
||||
self.attributes.show();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Display the sprite in Affine mode.
|
||||
pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self {
|
||||
assert!(
|
||||
self.affine_matrix.is_some(),
|
||||
"affine matrix must be set before enabling affine matrix!"
|
||||
);
|
||||
|
||||
self.attributes.show_affine(affine_mode);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal flip, note that this only has a visible affect in Normal mode.
|
||||
pub fn set_hflip(&mut self, flip: bool) -> &mut Self {
|
||||
self.attributes.set_hflip(flip);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the vertical flip, note that this only has a visible affect in Normal mode.
|
||||
pub fn set_vflip(&mut self, flip: bool) -> &mut Self {
|
||||
self.attributes.set_vflip(flip);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the priority of the object relative to the backgrounds priority.
|
||||
pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
|
||||
self.attributes.set_priority(priority);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Changes the sprite mode to be hidden, can be changed to Normal or Affine
|
||||
/// modes using [`show`][ObjectUnmanaged::show] and
|
||||
/// [`show_affine`][ObjectUnmanaged::show_affine] respectively.
|
||||
pub fn hide(&mut self) -> &mut Self {
|
||||
self.attributes.hide();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the x position of the object.
|
||||
pub fn set_x(&mut self, x: u16) -> &mut Self {
|
||||
self.attributes.set_x(x);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the y position of the object.
|
||||
pub fn set_y(&mut self, y: u16) -> &mut Self {
|
||||
self.attributes.set_y(y);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the position of the object.
|
||||
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
|
||||
self.set_y(position.y.rem_euclid(1 << 9) as u16);
|
||||
self.set_x(position.x.rem_euclid(1 << 9) as u16);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the affine matrix. This only has an affect in Affine mode.
|
||||
pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self {
|
||||
let vram = affine_matrix.vram();
|
||||
self.affine_matrix = Some(vram);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn set_sprite_attributes(&mut self, sprite: &SpriteVram) -> &mut Self {
|
||||
let size = sprite.size();
|
||||
let (shape, size) = size.shape_size();
|
||||
|
||||
self.attributes.set_sprite(sprite.location(), shape, size);
|
||||
self.attributes.set_palette(sprite.palette_location());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the current sprite for the object.
|
||||
pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self {
|
||||
self.set_sprite_attributes(&sprite);
|
||||
|
||||
self.sprite = sprite;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
display::object::{Graphics, Tag},
|
||||
include_aseprite,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test_case]
|
||||
fn object_usage(gba: &mut crate::Gba) {
|
||||
const GRAPHICS: &Graphics = include_aseprite!(
|
||||
"../examples/the-purple-night/gfx/objects.aseprite",
|
||||
"../examples/the-purple-night/gfx/boss.aseprite"
|
||||
);
|
||||
|
||||
const BOSS: &Tag = GRAPHICS.tags().get("Boss");
|
||||
|
||||
let (mut gfx, mut loader) = gba.display.object.get_unmanaged();
|
||||
|
||||
{
|
||||
let mut slotter = gfx.iter();
|
||||
|
||||
let slot_a = slotter.next().unwrap();
|
||||
let slot_b = slotter.next().unwrap();
|
||||
|
||||
let mut obj = ObjectUnmanaged::new(loader.get_vram_sprite(BOSS.sprite(2)));
|
||||
|
||||
obj.show();
|
||||
|
||||
slot_b.set(&obj);
|
||||
slot_a.set(&obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
use core::alloc::Layout;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct Palette16 {
|
||||
|
@ -12,7 +14,6 @@ impl Palette16 {
|
|||
|
||||
// Clippy bug: claims that index is only used in recursion. I can't reproduce in
|
||||
// other examples, even just copy pasting this struct and impl into a blank project :/
|
||||
#[allow(clippy::only_used_in_recursion)]
|
||||
pub fn update_colour(&mut self, index: usize, colour: u16) {
|
||||
self.colours[index] = colour;
|
||||
}
|
||||
|
@ -21,4 +22,17 @@ impl Palette16 {
|
|||
pub fn colour(&self, index: usize) -> u16 {
|
||||
self.colours[index]
|
||||
}
|
||||
|
||||
pub(crate) const fn layout() -> Layout {
|
||||
Layout::new::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! include_palette {
|
||||
($palette:literal) => {
|
||||
$crate::include_colours_inner!($palette)
|
||||
};
|
||||
}
|
||||
|
||||
pub use include_palette;
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
)]
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(adt_const_params)]
|
||||
#![feature(alloc_error_handler)]
|
||||
#![feature(allocator_api)]
|
||||
#![feature(asm_const)]
|
||||
#![warn(clippy::all)]
|
||||
|
@ -37,43 +36,28 @@
|
|||
|
||||
/// This macro is used to convert a png or bmp into a format usable by the Game Boy Advance.
|
||||
///
|
||||
/// The macro expects to be linked to a `toml` file which contains a metadata about the image
|
||||
/// and a link to the png or bmp itself. See the examples below for a full definition of the format.
|
||||
/// Suppose you have a file in `examples/water_tiles.png` which contains some tiles you'd like to use.
|
||||
///
|
||||
/// # The manifest file format
|
||||
///
|
||||
/// The following is an example of the toml file you would need to create. Generally you will
|
||||
/// find this in the `gfx` folder in the same level as the `src` folder (see the examples).
|
||||
///
|
||||
/// Suppose that the following is in `examples/water_tiles.toml`.
|
||||
///
|
||||
/// ```toml
|
||||
/// version = "1.0"
|
||||
///
|
||||
/// [image.tiles]
|
||||
/// filename = "water_tiles.png"
|
||||
/// tile_size = "8x8"
|
||||
/// ```
|
||||
///
|
||||
/// You then import this using:
|
||||
/// You import them using:
|
||||
/// ```rust,no_run
|
||||
/// ##![no_std]
|
||||
/// ##![no_main]
|
||||
/// agb::include_gfx!("examples/water_tiles.toml");
|
||||
/// agb::include_background_gfx!(water_tiles, tiles => "examples/water_tiles.png");
|
||||
/// ```
|
||||
///
|
||||
/// This will generate something along the lines of the following:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // module name comes from the name of the toml file, so `water_tiles` in this case because it is
|
||||
/// // called `water_tiles.toml`
|
||||
/// // module name comes from the first argument, name of the constant from the arrow
|
||||
/// mod water_tiles {
|
||||
/// const tiles = /* ... */;
|
||||
/// pub const tiles = /* ... */;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// And tiles will be an instance of [`TileData`][crate::display::tile_data::TileData]
|
||||
///
|
||||
/// You can import multiple files at once, and the palette data will be combined so they can all be visible.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Assume the tiles are loaded as above
|
||||
|
@ -88,13 +72,13 @@
|
|||
/// tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, TiledMap, VRamManager},
|
||||
/// Priority,
|
||||
/// },
|
||||
/// include_gfx,
|
||||
/// include_background_gfx,
|
||||
/// };
|
||||
///
|
||||
/// agb::include_gfx!("examples/water_tiles.toml");
|
||||
/// agb::include_background_gfx!(water_tiles, tiles => "examples/water_tiles.png");
|
||||
///
|
||||
/// # fn load_tileset(mut gfx: Tiled0, mut vram: VRamManager) {
|
||||
/// let tileset = TileSet::new(water_tiles::water_tiles.tiles, TileFormat::FourBpp);
|
||||
/// let tileset = TileSet::new(water_tiles::tiles.tiles, TileFormat::FourBpp);
|
||||
///
|
||||
/// vram.set_background_palettes(water_tiles::PALETTES);
|
||||
///
|
||||
|
@ -114,7 +98,7 @@
|
|||
/// bg.show();
|
||||
/// # }
|
||||
/// ```
|
||||
pub use agb_image_converter::include_gfx;
|
||||
pub use agb_image_converter::include_background_gfx;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use agb_image_converter::include_aseprite_inner;
|
||||
|
@ -122,6 +106,9 @@ pub use agb_image_converter::include_aseprite_inner;
|
|||
#[doc(hidden)]
|
||||
pub use agb_image_converter::include_font as include_font_inner;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use agb_image_converter::include_colours_inner;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! include_font {
|
||||
($font_path: literal, $font_size: literal) => {{
|
||||
|
@ -169,7 +156,7 @@ pub mod mgba;
|
|||
#[doc(inline)]
|
||||
pub use agb_fixnum as fixnum;
|
||||
/// Contains an implementation of a hashmap which suits the gameboy advance's hardware.
|
||||
pub mod hash_map;
|
||||
pub use agb_hashmap as hash_map;
|
||||
/// Simple random number generator
|
||||
pub mod rng;
|
||||
pub mod save;
|
||||
|
@ -185,6 +172,13 @@ pub mod syscall;
|
|||
/// Interactions with the internal timers
|
||||
pub mod timer;
|
||||
|
||||
mod no_game;
|
||||
|
||||
/// Default game
|
||||
pub use no_game::no_game;
|
||||
|
||||
pub(crate) mod arena;
|
||||
|
||||
pub use {agb_alloc::ExternalAllocator, agb_alloc::InternalAllocator};
|
||||
|
||||
#[cfg(not(any(test, feature = "testing")))]
|
||||
|
@ -380,6 +374,14 @@ pub mod test_runner {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn program_counter_before_interrupt() -> u32 {
|
||||
extern "C" {
|
||||
static mut agb_rs__program_counter: u32;
|
||||
}
|
||||
unsafe { agb_rs__program_counter }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Gba;
|
||||
|
@ -464,11 +466,3 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn program_counter_before_interrupt() -> u32 {
|
||||
extern "C" {
|
||||
static mut agb_rs__program_counter: u32;
|
||||
}
|
||||
unsafe { agb_rs__program_counter }
|
||||
}
|
||||
|
|
213
agb/src/no_game.rs
Normal file
|
@ -0,0 +1,213 @@
|
|||
//! The no game screen is what is displayed if there isn't a game made yet.
|
||||
|
||||
use agb_fixnum::{num, Num, Vector2D};
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{boxed::Box, vec};
|
||||
|
||||
use crate::display::object::{DynamicSprite, PaletteVram, Size, SpriteVram};
|
||||
use crate::display::palette16::Palette16;
|
||||
use crate::{
|
||||
display::{object::ObjectUnmanaged, HEIGHT, WIDTH},
|
||||
include_palette,
|
||||
interrupt::VBlank,
|
||||
};
|
||||
|
||||
const PALETTE: &[u16] = &include_palette!("gfx/pastel.png");
|
||||
|
||||
fn letters() -> Vec<Vec<Vector2D<Num<i32, 8>>>> {
|
||||
vec![
|
||||
// N
|
||||
vec![
|
||||
(num!(0.), num!(0.)).into(),
|
||||
(num!(1.), num!(1.)).into(),
|
||||
(num!(2.), num!(2.)).into(),
|
||||
(num!(3.), num!(3.)).into(),
|
||||
(num!(0.), num!(1.)).into(),
|
||||
(num!(0.), num!(2.)).into(),
|
||||
(num!(0.), num!(3.)).into(),
|
||||
(num!(3.), num!(0.)).into(),
|
||||
(num!(3.), num!(1.)).into(),
|
||||
(num!(3.), num!(2.)).into(),
|
||||
(num!(3.), num!(3.)).into(),
|
||||
],
|
||||
// O
|
||||
vec![
|
||||
(num!(0.), num!(0.)).into(),
|
||||
(num!(0.), num!(1.)).into(),
|
||||
(num!(0.), num!(2.)).into(),
|
||||
(num!(0.), num!(3.)).into(),
|
||||
(num!(1.), num!(3.)).into(),
|
||||
(num!(2.), num!(3.)).into(),
|
||||
(num!(3.), num!(3.)).into(),
|
||||
(num!(3.), num!(2.)).into(),
|
||||
(num!(3.), num!(1.)).into(),
|
||||
(num!(3.), num!(0.)).into(),
|
||||
(num!(2.), num!(0.)).into(),
|
||||
(num!(1.), num!(0.)).into(),
|
||||
],
|
||||
// G
|
||||
vec![
|
||||
(num!(3.), num!(0.)).into(),
|
||||
(num!(2.), num!(0.)).into(),
|
||||
(num!(1.), num!(0.)).into(),
|
||||
(num!(0.), num!(0.)).into(),
|
||||
(num!(0.), num!(1.)).into(),
|
||||
(num!(0.), num!(2.)).into(),
|
||||
(num!(0.), num!(3.)).into(),
|
||||
(num!(1.), num!(3.)).into(),
|
||||
(num!(2.), num!(3.)).into(),
|
||||
(num!(3.), num!(3.)).into(),
|
||||
(num!(3.), num!(2.25)).into(),
|
||||
(num!(3.), num!(1.5)).into(),
|
||||
(num!(2.), num!(1.5)).into(),
|
||||
],
|
||||
// A
|
||||
vec![
|
||||
(num!(0.), num!(0.)).into(),
|
||||
(num!(0.), num!(1.)).into(),
|
||||
(num!(0.), num!(2.)).into(),
|
||||
(num!(0.), num!(3.)).into(),
|
||||
(num!(3.), num!(3.)).into(),
|
||||
(num!(3.), num!(2.)).into(),
|
||||
(num!(3.), num!(1.)).into(),
|
||||
(num!(3.), num!(0.)).into(),
|
||||
(num!(2.), num!(0.)).into(),
|
||||
(num!(1.), num!(0.)).into(),
|
||||
(num!(1.), num!(1.5)).into(),
|
||||
(num!(2.), num!(1.5)).into(),
|
||||
],
|
||||
// M
|
||||
vec![
|
||||
(num!(0.), num!(0.)).into(),
|
||||
(num!(0.), num!(1.)).into(),
|
||||
(num!(0.), num!(2.)).into(),
|
||||
(num!(0.), num!(3.)).into(),
|
||||
(num!(3.), num!(3.)).into(),
|
||||
(num!(3.), num!(2.)).into(),
|
||||
(num!(3.), num!(1.)).into(),
|
||||
(num!(3.), num!(0.)).into(),
|
||||
(num!(1.5), num!(1.5)).into(),
|
||||
(num!(0.75), num!(0.75)).into(),
|
||||
(num!(2.25), num!(0.75)).into(),
|
||||
],
|
||||
// E
|
||||
vec![
|
||||
(num!(0.), num!(0.)).into(),
|
||||
(num!(0.), num!(1.)).into(),
|
||||
(num!(0.), num!(2.)).into(),
|
||||
(num!(0.), num!(3.)).into(),
|
||||
(num!(1.), num!(3.)).into(),
|
||||
(num!(2.), num!(3.)).into(),
|
||||
(num!(3.), num!(3.)).into(),
|
||||
(num!(3.), num!(0.)).into(),
|
||||
(num!(2.), num!(0.)).into(),
|
||||
(num!(1.), num!(0.)).into(),
|
||||
(num!(1.), num!(1.5)).into(),
|
||||
(num!(2.), num!(1.5)).into(),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
fn generate_sprites() -> Box<[SpriteVram]> {
|
||||
let mut sprites = Vec::new();
|
||||
|
||||
// generate palettes
|
||||
|
||||
let palettes: Vec<PaletteVram> = PALETTE
|
||||
.chunks(15)
|
||||
.map(|x| {
|
||||
core::iter::once(0)
|
||||
.chain(x.iter().copied())
|
||||
.chain(core::iter::repeat(0))
|
||||
.take(16)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.map(|palette| {
|
||||
let palette = Palette16::new(palette.try_into().unwrap());
|
||||
PaletteVram::new(&palette).unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// generate sprites
|
||||
let mut sprite = DynamicSprite::new(Size::S8x8);
|
||||
for (palette, colour) in (0..PALETTE.len()).map(|x| (x / 15, x % 15)) {
|
||||
for y in 0..8 {
|
||||
for x in 0..8 {
|
||||
sprite.set_pixel(x, y, colour + 1);
|
||||
}
|
||||
}
|
||||
sprites.push(sprite.to_vram(palettes[palette].clone()));
|
||||
}
|
||||
|
||||
sprites.into_boxed_slice()
|
||||
}
|
||||
|
||||
pub fn no_game(mut gba: crate::Gba) -> ! {
|
||||
let (mut oam, _) = gba.display.object.get_unmanaged();
|
||||
|
||||
let squares = generate_sprites();
|
||||
|
||||
let mut letter_positons = Vec::new();
|
||||
|
||||
let square_positions = {
|
||||
let mut s = letters();
|
||||
for letter in s.iter_mut() {
|
||||
letter.sort_by_key(|a| a.magnitude_squared());
|
||||
}
|
||||
s
|
||||
};
|
||||
for (letter_idx, letter_parts) in square_positions.iter().enumerate() {
|
||||
for part in letter_parts.iter() {
|
||||
let position = part
|
||||
.hadamard((8, 10).into())
|
||||
.hadamard((num!(3.) / 2, num!(3.) / 2).into());
|
||||
|
||||
let letter_pos = Vector2D::new(
|
||||
60 * (1 + letter_idx as i32 - ((letter_idx >= 2) as i32 * 3)),
|
||||
70 * ((letter_idx >= 2) as i32),
|
||||
);
|
||||
|
||||
letter_positons.push(position + letter_pos.change_base());
|
||||
}
|
||||
}
|
||||
|
||||
let bottom_right = letter_positons
|
||||
.iter()
|
||||
.copied()
|
||||
.max_by_key(|x| x.manhattan_distance())
|
||||
.unwrap();
|
||||
|
||||
let difference = (Vector2D::new(WIDTH - 8, HEIGHT - 8).change_base() - bottom_right) / 2;
|
||||
|
||||
for pos in letter_positons.iter_mut() {
|
||||
*pos += difference;
|
||||
}
|
||||
|
||||
let mut time: Num<i32, 8> = num!(0.);
|
||||
let time_delta: Num<i32, 8> = num!(0.025);
|
||||
|
||||
let vblank = VBlank::get();
|
||||
|
||||
loop {
|
||||
time += time_delta;
|
||||
time %= 1;
|
||||
let letters: Vec<ObjectUnmanaged> = letter_positons
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, position)| {
|
||||
let time = time + Num::<i32, 8>::new(idx as i32) / 128;
|
||||
(idx, *position + Vector2D::new(time.sin(), time.cos()) * 10)
|
||||
})
|
||||
.map(|(idx, pos)| {
|
||||
let mut obj = ObjectUnmanaged::new(squares[idx % squares.len()].clone());
|
||||
obj.show().set_position(pos.floor());
|
||||
obj
|
||||
})
|
||||
.collect();
|
||||
|
||||
vblank.wait_for_vblank();
|
||||
for (obj, slot) in letters.iter().zip(oam.iter()) {
|
||||
slot.set(obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,9 @@ agb_thumb_func agb_rs__WramVerifyBuf
|
|||
@ Returns from the function successfully
|
||||
movs r0, #1
|
||||
0: @ Jumps to here return the function unsuccessfully, because r0 contains 0 at this point
|
||||
pop {r4-r5, pc}
|
||||
pop {r4-r5}
|
||||
pop {r1}
|
||||
bx r1
|
||||
agb_thumb_end agb_rs__WramVerifyBuf
|
||||
|
||||
|
||||
|
|
173
book/games/pong/Cargo.lock
generated
|
@ -16,65 +16,71 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
|||
|
||||
[[package]]
|
||||
name = "agb"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
"bare-metal",
|
||||
"bitflags 2.0.2",
|
||||
"bitflags 2.1.0",
|
||||
"modular-bitfield",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_fixnum"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"asefile",
|
||||
"fontdue",
|
||||
"image",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.8",
|
||||
"toml",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -113,9 +119,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.0.2"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
|
||||
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
|
@ -172,40 +178,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
|
||||
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
|
@ -227,22 +216,6 @@ dependencies = [
|
|||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
|
@ -252,12 +225,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
|
@ -371,9 +338,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.53"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -393,35 +360,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -441,49 +379,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.8"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
|
@ -501,18 +405,3 @@ name = "version_check"
|
|||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -4,16 +4,15 @@ version = "0.1.0"
|
|||
authors = ["Gwilym Kuiper <gw@ilym.me>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { version = "0.14.0", path = "../../../agb" }
|
||||
agb = { version = "0.15.0", path = "../../../agb" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
|
@ -32,7 +32,7 @@ const BALL: &Tag = GRAPHICS.tags().get("Ball");
|
|||
#[agb::entry]
|
||||
fn main(mut gba: agb::Gba) -> ! {
|
||||
// Get the OAM manager
|
||||
let object = gba.display.object.get();
|
||||
let object = gba.display.object.get_managed();
|
||||
|
||||
// Create an object with the ball sprite
|
||||
let mut ball = object.object_sprite(BALL.sprite(0));
|
||||
|
|
14
examples/amplitude/.cargo/config.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
|
||||
[build]
|
||||
target = "thumbv4t-none-eabi"
|
||||
|
||||
[target.thumbv4t-none-eabi]
|
||||
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
|
||||
runner = "mgba-qt"
|
||||
|
||||
[target.armv4t-none-eabi]
|
||||
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
|
||||
runner = "mgba-qt"
|
407
examples/amplitude/Cargo.lock
generated
Normal file
|
@ -0,0 +1,407 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "agb"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
"bare-metal",
|
||||
"bitflags 2.1.0",
|
||||
"modular-bitfield",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_fixnum"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"asefile",
|
||||
"fontdue",
|
||||
"image",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_macros"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amplitude"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asefile"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a71de7aecd2d0a76ec90fde2c443d12667c737d92de76bd187f101eca37891"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"flate2",
|
||||
"image",
|
||||
"log",
|
||||
"nohash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bare-metal"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.23.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modular-bitfield"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74"
|
||||
dependencies = [
|
||||
"modular-bitfield-impl",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modular-bitfield-impl"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.16.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"miniz_oxide 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
20
examples/amplitude/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "amplitude"
|
||||
version = "0.1.0"
|
||||
authors = [""]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { version = "0.15.0", path = "../../agb" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
debug = true
|
||||
codegen-units = 1
|
72
examples/amplitude/README.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
# AGBRS template
|
||||
|
||||
## A basic template example for agb projects
|
||||
|
||||
This makes getting started with a new project for the Game Boy Advance in rust really simple, by providing
|
||||
all the boiler plate files for you.
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You will need the following installed in order to build and run this project:
|
||||
|
||||
* A recent version of `rustup`. See the [rust website](https://www.rust-lang.org/tools/install) for instructions for your operating system
|
||||
* `arm-none-eabi-binutils` for assembling and linking
|
||||
* Windows: [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads).
|
||||
Make sure you select "Add path to environment variable" during the install
|
||||
* Debian and derivatives (e.g. Ubuntu, raspberry pi OS, linux mint): `sudo apt install binutils-arm-none-eabi`
|
||||
* Arch linux and derivatives: `sudo pacman -S arm-none-eabi-binutils`
|
||||
|
||||
You will also want to install an emulator. The best support in agb is with [mgba](https://mgba.io), with
|
||||
`println!` support via `agb::println!` but any emulator should work. You'll get the best experience if
|
||||
`mgba-qt` is in your `PATH`.
|
||||
|
||||
If you want to run your game on real hardware, you will also need to install `agb-gbafix` which you can do after installing
|
||||
rust with the following: `cargo install agb-gbafix`. This is not required if you are only running your game in an emulator.
|
||||
|
||||
### Running in an emulator
|
||||
|
||||
Once you have the prerequisites installed, you should be able to build using
|
||||
|
||||
```sh
|
||||
cargo build
|
||||
```
|
||||
|
||||
or in release mode (recommended for the final version to ship to players)
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The resulting file will be in `target/thumbv4t-none-eabi/debug/<your game>` or `target/thumbv4t-none-eabi/release/<your game>` depending on
|
||||
whether you did a release or debug build.
|
||||
|
||||
If you have `mgba-qt` in your path, you will be able to run your game with
|
||||
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
|
||||
or in release mode
|
||||
|
||||
```sh
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
## Starting development
|
||||
|
||||
You can find the documentation for agb [here](https://docs.rs/agb/latest/agb/).
|
||||
|
||||
You may also want to change the package name and version in `Cargo.toml` before you start.
|
||||
|
||||
## Shipping a .gba file for real hardware
|
||||
|
||||
To make a game run on real hardware, you will need to convert the built file into a file suitable for
|
||||
running on the real thing.
|
||||
|
||||
First build the binary in release mode using the instructions above, then do the following:
|
||||
|
||||
```sh
|
||||
agb-gbafix target/thumbv4t-none-eabi/release/<your game> -o <your game>.gba
|
||||
```
|
117
examples/amplitude/gba.ld
Normal file
|
@ -0,0 +1,117 @@
|
|||
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||
OUTPUT_ARCH(arm)
|
||||
|
||||
ENTRY(__start)
|
||||
EXTERN(__RUST_INTERRUPT_HANDLER)
|
||||
|
||||
EXTERN(__agbabi_memset)
|
||||
EXTERN(__agbabi_memcpy)
|
||||
|
||||
MEMORY {
|
||||
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
||||
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
|
||||
rom (rx) : ORIGIN = 0x08000000, LENGTH = 32M
|
||||
}
|
||||
|
||||
__text_start = ORIGIN(rom);
|
||||
|
||||
INPUT (agb.a)
|
||||
|
||||
SECTIONS {
|
||||
. = __text_start;
|
||||
|
||||
|
||||
.text : {
|
||||
KEEP(*(.crt0));
|
||||
*(.crt0 .crt0*);
|
||||
*(.text .text*);
|
||||
. = ALIGN(4);
|
||||
} > rom
|
||||
__text_end = .;
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*);
|
||||
. = ALIGN(4);
|
||||
} > rom
|
||||
|
||||
__iwram_rom_start = .;
|
||||
.iwram : {
|
||||
__iwram_data_start = ABSOLUTE(.);
|
||||
|
||||
*(.iwram .iwram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
*(.text_iwram .text_iwram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
__iwram_data_end = ABSOLUTE(.);
|
||||
} > iwram AT>rom
|
||||
|
||||
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
|
||||
|
||||
__ewram_rom_start = .;
|
||||
.ewram : {
|
||||
__ewram_data_start = ABSOLUTE(.);
|
||||
|
||||
*(.ewram .ewram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
*(.data .data.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
__ewram_data_end = ABSOLUTE(.);
|
||||
} > ewram AT>rom
|
||||
|
||||
.bss : {
|
||||
*(.bss .bss.*);
|
||||
. = ALIGN(4);
|
||||
__iwram_end = ABSOLUTE(.);
|
||||
} > iwram
|
||||
|
||||
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
|
||||
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
|
||||
|
||||
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
|
||||
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
|
||||
|
||||
.shstrtab : {
|
||||
*(.shstrtab)
|
||||
}
|
||||
|
||||
/* debugging sections */
|
||||
/* Stabs */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
.comment 0 : { *(.comment) }
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
/* SGI/MIPS DWARF 2 extensions */
|
||||
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||
.debug_typenames 0 : { *(.debug_typenames) }
|
||||
.debug_varnames 0 : { *(.debug_varnames) }
|
||||
|
||||
.debug_ranges 0 : { *(.debug_ranges) }
|
||||
|
||||
/* discard anything not already mentioned */
|
||||
/DISCARD/ : { *(*) }
|
||||
}
|
115
examples/amplitude/gba_mb.ld
Normal file
|
@ -0,0 +1,115 @@
|
|||
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||
OUTPUT_ARCH(arm)
|
||||
|
||||
ENTRY(__start)
|
||||
EXTERN(__RUST_INTERRUPT_HANDLER)
|
||||
|
||||
EXTERN(__agbabi_memset)
|
||||
EXTERN(__agbabi_memcpy)
|
||||
|
||||
MEMORY {
|
||||
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
||||
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
|
||||
}
|
||||
|
||||
__text_start = ORIGIN(ewram);
|
||||
|
||||
INPUT (agb.a)
|
||||
|
||||
SECTIONS {
|
||||
. = __text_start;
|
||||
|
||||
.text : {
|
||||
KEEP(*(.crt0));
|
||||
*(.crt0 .crt0*);
|
||||
*(.text .text*);
|
||||
. = ALIGN(4);
|
||||
} > rom
|
||||
__text_end = .;
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*);
|
||||
. = ALIGN(4);
|
||||
} > ewram
|
||||
|
||||
__iwram_rom_start = .;
|
||||
.iwram : {
|
||||
__iwram_data_start = ABSOLUTE(.);
|
||||
|
||||
*(.iwram .iwram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
*(.text_iwram .text_iwram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
__iwram_data_end = ABSOLUTE(.);
|
||||
} > iwram AT>ewram
|
||||
|
||||
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
|
||||
|
||||
__ewram_rom_start = .;
|
||||
.ewram : {
|
||||
__ewram_data_start = ABSOLUTE(.);
|
||||
|
||||
*(.ewram .ewram.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
*(.data .data.*);
|
||||
. = ALIGN(4);
|
||||
|
||||
__ewram_data_end = ABSOLUTE(.);
|
||||
} > ewram AT>ewram
|
||||
|
||||
.bss : {
|
||||
*(.bss .bss.*);
|
||||
. = ALIGN(4);
|
||||
__iwram_end = ABSOLUTE(.);
|
||||
} > iwram
|
||||
|
||||
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
|
||||
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
|
||||
|
||||
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
|
||||
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
|
||||
|
||||
.shstrtab : {
|
||||
*(.shstrtab)
|
||||
}
|
||||
|
||||
/* debugging sections */
|
||||
/* Stabs */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
.comment 0 : { *(.comment) }
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
/* SGI/MIPS DWARF 2 extensions */
|
||||
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||
.debug_typenames 0 : { *(.debug_typenames) }
|
||||
.debug_varnames 0 : { *(.debug_varnames) }
|
||||
|
||||
.debug_ranges 0 : { *(.debug_ranges) }
|
||||
|
||||
/* discard anything not already mentioned */
|
||||
/DISCARD/ : { *(*) }
|
||||
}
|
BIN
examples/amplitude/gfx/circles.aseprite
Normal file
BIN
examples/amplitude/gfx/numbers.aseprite
Normal file
BIN
examples/amplitude/gfx/saw.aseprite
Normal file
3
examples/amplitude/rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src", "clippy", "rustfmt"]
|
370
examples/amplitude/src/lib.rs
Normal file
|
@ -0,0 +1,370 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![cfg_attr(test, feature(custom_test_frameworks))]
|
||||
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
||||
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use agb::{
|
||||
display::{
|
||||
self,
|
||||
affine::AffineMatrix,
|
||||
object::{
|
||||
AffineMatrixInstance, AffineMode, Graphics, OamIterator, ObjectUnmanaged, Sprite,
|
||||
SpriteLoader, SpriteVram, Tag,
|
||||
},
|
||||
palette16::Palette16,
|
||||
},
|
||||
fixnum::{num, Num, Vector2D},
|
||||
include_aseprite,
|
||||
input::{Button, ButtonController},
|
||||
rng,
|
||||
};
|
||||
use alloc::{boxed::Box, collections::VecDeque, vec::Vec};
|
||||
|
||||
type Number = Num<i32, 8>;
|
||||
|
||||
struct Saw {
|
||||
object: ObjectUnmanaged,
|
||||
position: Vector2D<Number>,
|
||||
angle: Number,
|
||||
rotation_speed: Number,
|
||||
}
|
||||
|
||||
enum Colour {
|
||||
Red,
|
||||
Blue,
|
||||
}
|
||||
|
||||
struct Circle {
|
||||
colour: Colour,
|
||||
position: Vector2D<Number>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SpriteCache {
|
||||
saw: SpriteVram,
|
||||
blue: SpriteVram,
|
||||
red: SpriteVram,
|
||||
numbers: Box<[SpriteVram]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum DrawDirection {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
fn draw_number(
|
||||
mut number: u32,
|
||||
position: Vector2D<i32>,
|
||||
oam: &mut OamIterator,
|
||||
direction: DrawDirection,
|
||||
sprite_cache: &SpriteCache,
|
||||
) -> Option<()> {
|
||||
let mut digits = Vec::new();
|
||||
if number == 0 {
|
||||
digits.push(0);
|
||||
}
|
||||
|
||||
while number != 0 {
|
||||
digits.push(number % 10);
|
||||
number /= 10;
|
||||
}
|
||||
|
||||
let mut current_position = if direction == DrawDirection::Right {
|
||||
position + (4 * (digits.len() - 1) as i32, 0).into()
|
||||
} else {
|
||||
position
|
||||
};
|
||||
|
||||
for digit in digits {
|
||||
let mut obj = ObjectUnmanaged::new(sprite_cache.numbers[digit as usize].clone());
|
||||
obj.show().set_position(current_position);
|
||||
|
||||
oam.next()?.set(&obj);
|
||||
|
||||
current_position -= (4, 0).into();
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
impl SpriteCache {
|
||||
fn new(loader: &mut SpriteLoader) -> Self {
|
||||
const SPRITES: &Graphics = include_aseprite!(
|
||||
"gfx/circles.aseprite",
|
||||
"gfx/saw.aseprite",
|
||||
"gfx/numbers.aseprite"
|
||||
);
|
||||
|
||||
const NUMBERS: &Tag = SPRITES.tags().get("numbers");
|
||||
const BLUE_CIRCLE: &Sprite = SPRITES.tags().get("Blue").sprite(0);
|
||||
const RED_CIRCLE: &Sprite = SPRITES.tags().get("Red").sprite(0);
|
||||
const SAW: &Sprite = SPRITES.tags().get("Saw").sprite(0);
|
||||
|
||||
Self {
|
||||
saw: loader.get_vram_sprite(SAW),
|
||||
blue: loader.get_vram_sprite(BLUE_CIRCLE),
|
||||
red: loader.get_vram_sprite(RED_CIRCLE),
|
||||
numbers: (0..10)
|
||||
.map(|x| NUMBERS.sprite(x))
|
||||
.map(|x| loader.get_vram_sprite(x))
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Game {
|
||||
settings: FinalisedSettings,
|
||||
circles: VecDeque<Circle>,
|
||||
saws: VecDeque<Saw>,
|
||||
head_position: Vector2D<Number>,
|
||||
phase_time: Number,
|
||||
input: ButtonController,
|
||||
frame_since_last_saw: i32,
|
||||
alive_frames: u32,
|
||||
}
|
||||
|
||||
enum GameState {
|
||||
Continue,
|
||||
Loss(u32),
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn from_settings(settings: Settings) -> Self {
|
||||
let finalised = settings.to_finalised_settings();
|
||||
|
||||
let mut circles = VecDeque::with_capacity(finalised.number_of_circles);
|
||||
for idx in 0..finalised.number_of_circles {
|
||||
circles.push_back(Circle {
|
||||
colour: Colour::Red,
|
||||
position: Vector2D::new(
|
||||
finalised.speed * idx as i32 - 4,
|
||||
settings.head_start_position.y,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
Game {
|
||||
input: agb::input::ButtonController::new(),
|
||||
settings: finalised,
|
||||
circles,
|
||||
saws: VecDeque::new(),
|
||||
head_position: settings.head_start_position,
|
||||
phase_time: 0.into(),
|
||||
frame_since_last_saw: 0,
|
||||
alive_frames: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(&mut self, sprite_cache: &SpriteCache) -> GameState {
|
||||
self.input.update();
|
||||
|
||||
let (height, colour) = if self.input.is_pressed(Button::A) {
|
||||
(self.settings.wave_height_ability, Colour::Blue)
|
||||
} else {
|
||||
(self.settings.wave_height_normal, Colour::Red)
|
||||
};
|
||||
|
||||
let next_phase_time = self.phase_time + self.settings.phase_speed;
|
||||
|
||||
let this_frame_y_delta = next_phase_time.cos() - self.phase_time.cos();
|
||||
self.phase_time = next_phase_time % num!(1.);
|
||||
let this_frame_y_delta = this_frame_y_delta * height;
|
||||
self.head_position.y += this_frame_y_delta;
|
||||
|
||||
// update circles
|
||||
for circle in self.circles.iter_mut() {
|
||||
circle.position.x -= self.settings.speed;
|
||||
}
|
||||
|
||||
self.circles.pop_front();
|
||||
|
||||
// generate circle
|
||||
let circle = Circle {
|
||||
colour,
|
||||
position: self.head_position,
|
||||
};
|
||||
|
||||
self.circles.push_back(circle);
|
||||
|
||||
// update saws + check for death
|
||||
let mut saw_has_hit_head = false;
|
||||
let mut number_of_saws_to_pop = 0;
|
||||
for (idx, saw) in self.saws.iter_mut().enumerate() {
|
||||
saw.position.x -= self.settings.speed;
|
||||
if saw.position.x < (-32).into() {
|
||||
number_of_saws_to_pop = idx + 1;
|
||||
}
|
||||
saw.angle += saw.rotation_speed;
|
||||
|
||||
let angle_affine_matrix = AffineMatrix::from_rotation(saw.angle);
|
||||
|
||||
saw.object.set_affine_matrix(AffineMatrixInstance::new(
|
||||
angle_affine_matrix.to_object_wrapping(),
|
||||
));
|
||||
saw.object.show_affine(AffineMode::Affine);
|
||||
|
||||
saw.object
|
||||
.set_position(saw.position.floor() - (16, 16).into());
|
||||
|
||||
if (saw.position - self.head_position).magnitude_squared()
|
||||
< ((16 + 4) * (16 + 4)).into()
|
||||
{
|
||||
saw_has_hit_head = true;
|
||||
}
|
||||
}
|
||||
|
||||
// destroy saws
|
||||
for _ in 0..number_of_saws_to_pop {
|
||||
self.saws.pop_front();
|
||||
}
|
||||
|
||||
// create saw
|
||||
self.frame_since_last_saw -= 1;
|
||||
if self.frame_since_last_saw <= 0 {
|
||||
self.frame_since_last_saw = self.settings.frames_between_saws;
|
||||
let mut rotation_direction = rng::gen().signum();
|
||||
if rotation_direction == 0 {
|
||||
rotation_direction = 1;
|
||||
}
|
||||
|
||||
let rotation_magnitude =
|
||||
Number::from_raw(rng::gen().abs() % (1 << 8)) % num!(0.02) + num!(0.005);
|
||||
|
||||
let rotation_speed = rotation_magnitude * rotation_direction;
|
||||
let saw = Saw {
|
||||
object: ObjectUnmanaged::new(sprite_cache.saw.clone()),
|
||||
position: (300, rng::gen().rem_euclid(display::HEIGHT)).into(),
|
||||
angle: 0.into(),
|
||||
rotation_speed,
|
||||
};
|
||||
|
||||
self.saws.push_back(saw);
|
||||
}
|
||||
|
||||
self.alive_frames += 1;
|
||||
|
||||
let out_of_bounds_death = self.head_position.y.floor() < -4
|
||||
|| (self.head_position.y + 1).floor() > display::HEIGHT + 4;
|
||||
|
||||
if saw_has_hit_head || out_of_bounds_death {
|
||||
GameState::Loss(self.alive_frames)
|
||||
} else {
|
||||
GameState::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, oam: &mut OamIterator, sprite_cache: &SpriteCache) -> Option<()> {
|
||||
for saw in self.saws.iter() {
|
||||
oam.next()?.set(&saw.object);
|
||||
}
|
||||
|
||||
for circle in self.circles.iter() {
|
||||
let mut object = ObjectUnmanaged::new(match circle.colour {
|
||||
Colour::Red => sprite_cache.red.clone(),
|
||||
Colour::Blue => sprite_cache.blue.clone(),
|
||||
});
|
||||
|
||||
object
|
||||
.show()
|
||||
.set_position(circle.position.floor() - (4, 4).into());
|
||||
|
||||
oam.next()?.set(&object);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
phase_speed: Number,
|
||||
frames_between_saws: i32,
|
||||
speed: Number,
|
||||
head_start_position: Vector2D<Number>,
|
||||
wave_height_normal: Number,
|
||||
wave_height_ability: Number,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
fn to_finalised_settings(&self) -> FinalisedSettings {
|
||||
FinalisedSettings {
|
||||
number_of_circles: ((self.head_start_position.x + 4) / self.speed + 1)
|
||||
.floor()
|
||||
.try_into()
|
||||
.expect("number should be positive"),
|
||||
speed: self.speed,
|
||||
phase_speed: self.phase_speed,
|
||||
frames_between_saws: self.frames_between_saws,
|
||||
wave_height_ability: self.wave_height_ability,
|
||||
wave_height_normal: self.wave_height_normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FinalisedSettings {
|
||||
wave_height_normal: Number,
|
||||
wave_height_ability: Number,
|
||||
phase_speed: Number,
|
||||
frames_between_saws: i32,
|
||||
speed: Number,
|
||||
number_of_circles: usize,
|
||||
}
|
||||
|
||||
pub fn main(mut gba: agb::Gba) -> ! {
|
||||
let (mut unmanaged, mut sprites) = gba.display.object.get_unmanaged();
|
||||
let sprite_cache = SpriteCache::new(&mut sprites);
|
||||
|
||||
let (_background, mut vram) = gba.display.video.tiled0();
|
||||
|
||||
vram.set_background_palettes(&[Palette16::new([u16::MAX; 16])]);
|
||||
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
|
||||
let mut max_score = 0;
|
||||
|
||||
loop {
|
||||
let mut game = Game::from_settings(Settings {
|
||||
phase_speed: num!(0.02),
|
||||
frames_between_saws: 60,
|
||||
speed: num!(1.),
|
||||
head_start_position: (40, 100).into(),
|
||||
wave_height_normal: 20.into(),
|
||||
wave_height_ability: 5.into(),
|
||||
});
|
||||
loop {
|
||||
let state = game.frame(&sprite_cache);
|
||||
if game.alive_frames > max_score {
|
||||
max_score = game.alive_frames;
|
||||
}
|
||||
vblank.wait_for_vblank();
|
||||
let oam_frame = &mut unmanaged.iter();
|
||||
draw_number(
|
||||
max_score,
|
||||
(display::WIDTH - 4, 1).into(),
|
||||
oam_frame,
|
||||
DrawDirection::Left,
|
||||
&sprite_cache,
|
||||
);
|
||||
draw_number(
|
||||
game.alive_frames,
|
||||
(1, 1).into(),
|
||||
oam_frame,
|
||||
DrawDirection::Right,
|
||||
&sprite_cache,
|
||||
);
|
||||
game.render(oam_frame, &sprite_cache);
|
||||
|
||||
if let GameState::Loss(_) = state {
|
||||
for _ in 0..30 {
|
||||
vblank.wait_for_vblank();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
examples/amplitude/src/main.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![cfg_attr(test, feature(custom_test_frameworks))]
|
||||
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
||||
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
|
||||
|
||||
#[agb::entry]
|
||||
fn main(mut gba: agb::Gba) -> ! {
|
||||
amplitude::main(gba)
|
||||
}
|
175
examples/combo/Cargo.lock
generated
|
@ -16,69 +16,82 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
|||
|
||||
[[package]]
|
||||
name = "agb"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
"bare-metal",
|
||||
"bitflags 2.0.2",
|
||||
"bitflags 2.1.0",
|
||||
"modular-bitfield",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_fixnum"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"asefile",
|
||||
"fontdue",
|
||||
"image",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.8",
|
||||
"toml",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"cfg-if 1.0.0",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amplitude"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asefile"
|
||||
version = "0.3.5"
|
||||
|
@ -122,9 +135,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.0.2"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
|
||||
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
|
@ -161,6 +174,7 @@ name = "combo"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb",
|
||||
"amplitude",
|
||||
"hyperspace-roll",
|
||||
"the-hat-chooses-the-wizard",
|
||||
"the-purple-night",
|
||||
|
@ -197,11 +211,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
|
||||
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
|
@ -214,32 +228,15 @@ dependencies = [
|
|||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
|
@ -268,28 +265,12 @@ dependencies = [
|
|||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "0.1.27"
|
||||
|
@ -311,12 +292,6 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
|
@ -423,9 +398,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.53"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -459,44 +434,35 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.158"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.158"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
|
||||
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.94"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
|
||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -516,9 +482,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.8"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -561,40 +527,6 @@ dependencies = [
|
|||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
|
@ -613,21 +545,6 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
|
|
|
@ -6,18 +6,18 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { version = "0.14.0", path = "../../agb" }
|
||||
agb = { version = "0.15.0", path = "../../agb" }
|
||||
the-purple-night = { path = "../the-purple-night" }
|
||||
the-hat-chooses-the-wizard = { path = "../the-hat-chooses-the-wizard" }
|
||||
hyperspace-roll = { path = "../hyperspace-roll" }
|
||||
|
||||
amplitude = { path = "../amplitude" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
BIN
examples/combo/gfx/amplitude.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,16 +0,0 @@
|
|||
version = "1.0"
|
||||
|
||||
[image.hat]
|
||||
filename = "hat.png"
|
||||
tile_size = "8x8"
|
||||
transparent_colour = "121105"
|
||||
|
||||
[image.purple]
|
||||
filename = "purple.png"
|
||||
tile_size = "8x8"
|
||||
transparent_colour = "121105"
|
||||
|
||||
[image.hyperspace]
|
||||
filename = "hyperspace.png"
|
||||
tile_size = "8x8"
|
||||
transparent_colour = "121105"
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -12,7 +12,7 @@ use agb::{
|
|||
Priority,
|
||||
},
|
||||
fixnum::{Num, Vector2D},
|
||||
include_gfx,
|
||||
include_background_gfx,
|
||||
input::Button,
|
||||
};
|
||||
|
||||
|
@ -21,6 +21,7 @@ pub enum Game {
|
|||
TheHatChoosesTheWizard,
|
||||
ThePurpleNight,
|
||||
HyperspaceRoll,
|
||||
Amplitude,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
|
@ -29,20 +30,28 @@ impl Game {
|
|||
Game::TheHatChoosesTheWizard => the_hat_chooses_the_wizard::main(gba),
|
||||
Game::ThePurpleNight => the_purple_night::main(gba),
|
||||
Game::HyperspaceRoll => hyperspace_roll::main(gba),
|
||||
Game::Amplitude => amplitude::main(gba),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_index(index: i32) -> Game {
|
||||
match index.rem_euclid(3) {
|
||||
match index.rem_euclid(4) {
|
||||
0 => Game::TheHatChoosesTheWizard,
|
||||
1 => Game::ThePurpleNight,
|
||||
2 => Game::HyperspaceRoll,
|
||||
3 => Game::Amplitude,
|
||||
_ => unreachable!("game out of index in an unreachable manner"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include_gfx!("gfx/games.toml");
|
||||
include_background_gfx!(
|
||||
games, "121105",
|
||||
hat => "gfx/hat.png",
|
||||
purple => "gfx/purple.png",
|
||||
hyperspace => "gfx/hyperspace.png",
|
||||
amplitude => "gfx/amplitude.png"
|
||||
);
|
||||
|
||||
fn get_game(gba: &mut agb::Gba) -> Game {
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
|
@ -53,13 +62,15 @@ fn get_game(gba: &mut agb::Gba) -> Game {
|
|||
let hat = TileSet::new(games::hat.tiles, TileFormat::FourBpp);
|
||||
let purple = TileSet::new(games::purple.tiles, TileFormat::FourBpp);
|
||||
let hyperspace = TileSet::new(games::hyperspace.tiles, TileFormat::FourBpp);
|
||||
let amplitude = TileSet::new(games::amplitude.tiles, TileFormat::FourBpp);
|
||||
|
||||
let tiles = [hat, purple, hyperspace];
|
||||
let tiles = [hat, purple, hyperspace, amplitude];
|
||||
|
||||
let palette_assignments = &[
|
||||
games::hat.palette_assignments,
|
||||
games::purple.palette_assignments,
|
||||
games::hyperspace.palette_assignments,
|
||||
games::amplitude.palette_assignments,
|
||||
];
|
||||
|
||||
vram.set_background_palettes(games::PALETTES);
|
||||
|
@ -74,7 +85,7 @@ fn get_game(gba: &mut agb::Gba) -> Game {
|
|||
let y = pos.y.rem_euclid(20);
|
||||
let x = pos.x.rem_euclid(30);
|
||||
|
||||
let game = (pos.x).rem_euclid(90) as usize / 30;
|
||||
let game = (pos.x).rem_euclid(tiles.len() as i32 * 30) as usize / 30;
|
||||
let tile_id = (y * 30 + x) as usize;
|
||||
(
|
||||
&tiles[game],
|
||||
|
|
173
examples/hyperspace-roll/Cargo.lock
generated
|
@ -16,65 +16,71 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
|||
|
||||
[[package]]
|
||||
name = "agb"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
"bare-metal",
|
||||
"bitflags 2.0.2",
|
||||
"bitflags 2.1.0",
|
||||
"modular-bitfield",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_fixnum"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"asefile",
|
||||
"fontdue",
|
||||
"image",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.8",
|
||||
"toml",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -113,9 +119,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.0.2"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
|
||||
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
|
@ -172,40 +178,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
|
||||
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
|
@ -234,22 +223,6 @@ dependencies = [
|
|||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
|
@ -259,12 +232,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
|
@ -371,9 +338,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.53"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -393,35 +360,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -441,49 +379,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.8"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
|
@ -501,18 +405,3 @@ name = "version_check"
|
|||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -4,17 +4,15 @@ version = "0.1.0"
|
|||
authors = [""]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { version = "0.14.0", path = "../../agb" }
|
||||
agb = { version = "0.15.0", path = "../../agb" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
version = "1.0"
|
||||
transparent_colour = "121105"
|
||||
|
||||
[image.stars]
|
||||
filename = "stars.png"
|
||||
tile_size = "8x8"
|
||||
|
||||
[image.title]
|
||||
filename = "title-screen.png"
|
||||
tile_size = "8x8"
|
||||
|
||||
[image.help]
|
||||
filename = "help-text.png"
|
||||
tile_size = "8x8"
|
||||
|
||||
[image.descriptions1]
|
||||
filename = "descriptions1.png"
|
||||
tile_size = "8x8"
|
||||
|
||||
[image.descriptions2]
|
||||
filename = "descriptions2.png"
|
||||
tile_size = "8x8"
|
Before Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 550 B |
Before Width: | Height: | Size: 4.2 KiB |
|
@ -1,11 +1,17 @@
|
|||
use agb::{
|
||||
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager},
|
||||
include_gfx, rng,
|
||||
include_background_gfx, rng,
|
||||
};
|
||||
|
||||
use crate::sfx::Sfx;
|
||||
|
||||
include_gfx!("gfx/backgrounds.toml");
|
||||
include_background_gfx!(backgrounds, "121105",
|
||||
stars => "gfx/stars.aseprite",
|
||||
title => "gfx/title-screen.aseprite",
|
||||
help => "gfx/help-text.aseprite",
|
||||
descriptions1 => "gfx/descriptions1.png",
|
||||
descriptions2 => "gfx/descriptions2.png",
|
||||
);
|
||||
|
||||
pub fn load_palettes(vram: &mut VRamManager) {
|
||||
vram.set_background_palettes(backgrounds::PALETTES);
|
||||
|
|
|
@ -494,7 +494,7 @@ pub(crate) fn battle_screen(
|
|||
|
||||
let obj = &agb.obj;
|
||||
|
||||
let mut select_box_obj = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0)));
|
||||
let mut select_box_obj = agb.obj.object_sprite(SELECT_BOX.sprite(0));
|
||||
select_box_obj.show();
|
||||
|
||||
let num_dice = player_dice.dice.len();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use agb::display::object::{Object, ObjectController};
|
||||
use agb::display::object::{OamManaged, Object};
|
||||
use agb::rng;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
@ -39,7 +39,7 @@ pub struct BattleScreenDisplay<'a> {
|
|||
const HEALTH_BAR_WIDTH: usize = 48;
|
||||
|
||||
impl<'a> BattleScreenDisplay<'a> {
|
||||
pub fn new(obj: &'a ObjectController, current_battle_state: &CurrentBattleState) -> Self {
|
||||
pub fn new(obj: &'a OamManaged, current_battle_state: &CurrentBattleState) -> Self {
|
||||
let mut misc_sprites = vec![];
|
||||
let player_x = 12;
|
||||
let player_y = 8;
|
||||
|
@ -52,8 +52,8 @@ impl<'a> BattleScreenDisplay<'a> {
|
|||
Ship::PilotedShip
|
||||
});
|
||||
|
||||
let mut player_obj = obj.object(obj.sprite(player_sprite));
|
||||
let mut enemy_obj = obj.object(obj.sprite(enemy_sprite));
|
||||
let mut player_obj = obj.object_sprite(player_sprite);
|
||||
let mut enemy_obj = obj.object_sprite(enemy_sprite);
|
||||
|
||||
player_obj.set_x(player_x).set_y(player_y).set_z(1).show();
|
||||
enemy_obj.set_x(enemy_x).set_y(player_y).set_z(1).show();
|
||||
|
@ -66,7 +66,7 @@ impl<'a> BattleScreenDisplay<'a> {
|
|||
.faces_to_render()
|
||||
.enumerate()
|
||||
.map(|(i, (face, _))| {
|
||||
let mut die_obj = obj.object(obj.sprite(FACE_SPRITES.sprite_for_face(face)));
|
||||
let mut die_obj = obj.object_sprite(FACE_SPRITES.sprite_for_face(face));
|
||||
|
||||
die_obj.set_y(120).set_x(i as u16 * 40 + 28).show();
|
||||
|
||||
|
@ -89,7 +89,7 @@ impl<'a> BattleScreenDisplay<'a> {
|
|||
|
||||
let player_shield: Vec<_> = (0..5)
|
||||
.map(|i| {
|
||||
let mut shield_obj = obj.object(obj.sprite(shield_sprite));
|
||||
let mut shield_obj = obj.object_sprite(shield_sprite);
|
||||
shield_obj
|
||||
.set_x(player_x + 18 + 11 * i)
|
||||
.set_y(player_y)
|
||||
|
@ -101,7 +101,7 @@ impl<'a> BattleScreenDisplay<'a> {
|
|||
|
||||
let enemy_shield: Vec<_> = (0..5)
|
||||
.map(|i| {
|
||||
let mut shield_obj = obj.object(obj.sprite(shield_sprite));
|
||||
let mut shield_obj = obj.object_sprite(shield_sprite);
|
||||
shield_obj
|
||||
.set_x(enemy_x - 16 - 11 * i)
|
||||
.set_y(player_y)
|
||||
|
@ -146,9 +146,8 @@ impl<'a> BattleScreenDisplay<'a> {
|
|||
|
||||
let enemy_attack_display = (0..2)
|
||||
.map(|i| {
|
||||
let mut attack_obj = obj.object(
|
||||
obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack)),
|
||||
);
|
||||
let mut attack_obj = obj
|
||||
.object_sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack));
|
||||
|
||||
let attack_obj_position = (120, 56 + 32 * i).into();
|
||||
attack_obj.set_position(attack_obj_position).hide();
|
||||
|
@ -189,7 +188,7 @@ impl<'a> BattleScreenDisplay<'a> {
|
|||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
obj: &'a ObjectController,
|
||||
obj: &'a OamManaged,
|
||||
current_battle_state: &CurrentBattleState,
|
||||
) -> Vec<Action> {
|
||||
for (i, player_shield) in self.objs.player_shield.iter_mut().enumerate() {
|
||||
|
@ -279,7 +278,7 @@ impl<'a> BattleScreenDisplay<'a> {
|
|||
actions_to_apply
|
||||
}
|
||||
|
||||
pub fn add_action(&mut self, action: Action, obj: &'a ObjectController, sfx: &mut Sfx) {
|
||||
pub fn add_action(&mut self, action: Action, obj: &'a OamManaged, sfx: &mut Sfx) {
|
||||
play_sound_for_action_start(&action, sfx);
|
||||
|
||||
self.animations
|
||||
|
@ -309,7 +308,7 @@ impl<'a> EnemyAttackDisplay<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, attack: &Option<EnemyAttackState>, obj: &'a ObjectController) {
|
||||
pub fn update(&mut self, attack: &Option<EnemyAttackState>, obj: &'a OamManaged) {
|
||||
if let Some(attack) = attack {
|
||||
self.face.show().set_sprite(
|
||||
obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(attack.attack_type())),
|
||||
|
@ -350,27 +349,27 @@ enum AnimationUpdateState {
|
|||
}
|
||||
|
||||
impl<'a> AnimationStateHolder<'a> {
|
||||
fn for_action(a: Action, obj: &'a ObjectController) -> Self {
|
||||
fn for_action(a: Action, obj: &'a OamManaged) -> Self {
|
||||
let state = match a {
|
||||
Action::PlayerActivateShield { amount, .. } => {
|
||||
AnimationState::PlayerActivateShield { amount, frame: 0 }
|
||||
}
|
||||
Action::PlayerShoot { .. } => AnimationState::PlayerShoot {
|
||||
bullet: obj.object(obj.sprite(BULLET_SPRITE)),
|
||||
bullet: obj.object_sprite(BULLET_SPRITE),
|
||||
x: 64,
|
||||
},
|
||||
Action::PlayerDisrupt { .. } => AnimationState::PlayerDisrupt {
|
||||
bullet: obj.object(obj.sprite(DISRUPT_BULLET)),
|
||||
bullet: obj.object_sprite(DISRUPT_BULLET),
|
||||
x: 64,
|
||||
},
|
||||
Action::PlayerHeal { .. } => AnimationState::PlayerHeal {},
|
||||
Action::PlayerBurstShield { .. } => AnimationState::PlayerBurstShield { frame: 0 },
|
||||
Action::PlayerSendBurstShield { .. } => AnimationState::PlayerSendBurstShield {
|
||||
bullet: obj.object(obj.sprite(BURST_BULLET)),
|
||||
bullet: obj.object_sprite(BURST_BULLET),
|
||||
x: 64,
|
||||
},
|
||||
Action::EnemyShoot { .. } => AnimationState::EnemyShoot {
|
||||
bullet: obj.object(obj.sprite(BULLET_SPRITE)),
|
||||
bullet: obj.object_sprite(BULLET_SPRITE),
|
||||
x: 175,
|
||||
},
|
||||
Action::EnemyShield { amount, .. } => AnimationState::EnemyShield { amount, frame: 0 },
|
||||
|
@ -383,7 +382,7 @@ impl<'a> AnimationStateHolder<'a> {
|
|||
fn update(
|
||||
&mut self,
|
||||
objs: &mut BattleScreenDisplayObjects<'a>,
|
||||
obj: &'a ObjectController,
|
||||
obj: &'a OamManaged,
|
||||
current_battle_state: &CurrentBattleState,
|
||||
) -> AnimationUpdateState {
|
||||
match &mut self.state {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use agb::{
|
||||
display::{
|
||||
object::{Object, ObjectController},
|
||||
object::{OamManaged, Object},
|
||||
tiled::{RegularMap, TiledMap},
|
||||
HEIGHT, WIDTH,
|
||||
},
|
||||
|
@ -91,10 +91,10 @@ fn move_net_position_ud(idx: usize, direction: Tri) -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_dice_display<'a>(gfx: &'a ObjectController, dice: &'_ PlayerDice) -> Vec<Object<'a>> {
|
||||
fn create_dice_display<'a>(gfx: &'a OamManaged, dice: &'_ PlayerDice) -> Vec<Object<'a>> {
|
||||
let mut objects = Vec::new();
|
||||
for (idx, dice) in dice.dice.iter().enumerate() {
|
||||
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(dice.faces[1])));
|
||||
let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(dice.faces[1]));
|
||||
obj.set_x((idx as i32 * 32 - 24 / 2 + 20) as u16);
|
||||
obj.set_y(16 - 24 / 2);
|
||||
|
||||
|
@ -105,10 +105,10 @@ fn create_dice_display<'a>(gfx: &'a ObjectController, dice: &'_ PlayerDice) -> V
|
|||
objects
|
||||
}
|
||||
|
||||
fn create_net<'a>(gfx: &'a ObjectController, die: &'_ Die, modified: &[usize]) -> Vec<Object<'a>> {
|
||||
fn create_net<'a>(gfx: &'a OamManaged, die: &'_ Die, modified: &[usize]) -> Vec<Object<'a>> {
|
||||
let mut objects = Vec::new();
|
||||
for (idx, &face) in die.faces.iter().enumerate() {
|
||||
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(face)));
|
||||
let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(face));
|
||||
let (x, y) = screen_position_for_index(idx);
|
||||
obj.set_x((x - 24 / 2) as u16);
|
||||
obj.set_y((y - 24 / 2) as u16);
|
||||
|
@ -119,7 +119,7 @@ fn create_net<'a>(gfx: &'a ObjectController, die: &'_ Die, modified: &[usize]) -
|
|||
}
|
||||
|
||||
for &m in modified.iter().chain(core::iter::once(&3)) {
|
||||
let mut obj = gfx.object(gfx.sprite(MODIFIED_BOX));
|
||||
let mut obj = gfx.object_sprite(MODIFIED_BOX);
|
||||
let (x, y) = screen_position_for_index(m);
|
||||
obj.set_x((x - 32 / 2) as u16);
|
||||
obj.set_y((y - 32 / 2) as u16);
|
||||
|
@ -139,10 +139,10 @@ fn upgrade_position(idx: usize) -> (u32, u32) {
|
|||
)
|
||||
}
|
||||
|
||||
fn create_upgrade_objects<'a>(gfx: &'a ObjectController, upgrades: &[Face]) -> Vec<Object<'a>> {
|
||||
fn create_upgrade_objects<'a>(gfx: &'a OamManaged, upgrades: &[Face]) -> Vec<Object<'a>> {
|
||||
let mut objects = Vec::new();
|
||||
for (idx, &upgrade) in upgrades.iter().enumerate() {
|
||||
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(upgrade)));
|
||||
let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(upgrade));
|
||||
let (x, y) = upgrade_position(idx);
|
||||
obj.set_x((x - 24 / 2) as u16);
|
||||
obj.set_y((y - 24 / 2) as u16);
|
||||
|
@ -180,13 +180,13 @@ pub(crate) fn customise_screen(
|
|||
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
|
||||
let mut select_box = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0)));
|
||||
let mut select_box = agb.obj.object_sprite(SELECT_BOX.sprite(0));
|
||||
|
||||
select_box.show();
|
||||
|
||||
let mut selected_dice = agb.obj.object(agb.obj.sprite(SELECTED_BOX));
|
||||
let mut selected_dice = agb.obj.object_sprite(SELECTED_BOX);
|
||||
selected_dice.hide();
|
||||
let mut selected_face = agb.obj.object(agb.obj.sprite(SELECTED_BOX));
|
||||
let mut selected_face = agb.obj.object_sprite(SELECTED_BOX);
|
||||
selected_face.hide();
|
||||
agb.sfx.frame();
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use agb::{
|
||||
display::object::{Object, ObjectController, Sprite, Tag},
|
||||
display::object::{OamManaged, Object, Sprite, Tag},
|
||||
fixnum::Vector2D,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
@ -141,14 +141,12 @@ pub struct HealthBar<'a> {
|
|||
}
|
||||
|
||||
impl<'a> HealthBar<'a> {
|
||||
pub fn new(pos: Vector2D<i32>, max: usize, obj: &'a ObjectController) -> Self {
|
||||
pub fn new(pos: Vector2D<i32>, max: usize, obj: &'a OamManaged) -> Self {
|
||||
assert_eq!(max % 8, 0);
|
||||
|
||||
let sprites = (0..(max / 8))
|
||||
.map(|i| {
|
||||
let health_sprite = obj.sprite(SMALL_SPRITES.red_bar(0));
|
||||
|
||||
let mut health_object = obj.object(health_sprite);
|
||||
let mut health_object = obj.object_sprite(SMALL_SPRITES.red_bar(0));
|
||||
health_object
|
||||
.set_position(pos + (i as i32 * 8, 0).into())
|
||||
.show();
|
||||
|
@ -159,7 +157,7 @@ impl<'a> HealthBar<'a> {
|
|||
Self { max, sprites }
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, new_value: usize, obj: &'a ObjectController) {
|
||||
pub fn set_value(&mut self, new_value: usize, obj: &'a OamManaged) {
|
||||
assert!(new_value <= self.max);
|
||||
|
||||
for (i, sprite) in self.sprites.iter_mut().enumerate() {
|
||||
|
@ -195,22 +193,22 @@ pub struct FractionDisplay<'a> {
|
|||
}
|
||||
|
||||
impl<'a> FractionDisplay<'a> {
|
||||
pub fn new(pos: Vector2D<i32>, digits: usize, obj: &'a ObjectController) -> Self {
|
||||
pub fn new(pos: Vector2D<i32>, digits: usize, obj: &'a OamManaged) -> Self {
|
||||
let mut sprites = Vec::with_capacity(digits * 2 + 1);
|
||||
|
||||
for i in 0..digits {
|
||||
let mut left_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
|
||||
let mut left_digit = obj.object_sprite(SMALL_SPRITES.number(0));
|
||||
left_digit.set_position(pos + (i as i32 * 4, 0).into());
|
||||
|
||||
sprites.push(left_digit);
|
||||
|
||||
let mut right_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
|
||||
let mut right_digit = obj.object_sprite(SMALL_SPRITES.number(0));
|
||||
right_digit.set_position(pos + (i as i32 * 4 + digits as i32 * 4 + 7, 0).into());
|
||||
|
||||
sprites.push(right_digit);
|
||||
}
|
||||
|
||||
let mut slash = obj.object(obj.sprite(SMALL_SPRITES.slash()));
|
||||
let mut slash = obj.object_sprite(SMALL_SPRITES.slash());
|
||||
slash.set_position(pos + (digits as i32 * 4 + 1, 0).into());
|
||||
sprites.push(slash);
|
||||
|
||||
|
@ -222,7 +220,7 @@ impl<'a> FractionDisplay<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, current: usize, max: usize, obj: &'a ObjectController) {
|
||||
pub fn set_value(&mut self, current: usize, max: usize, obj: &'a OamManaged) {
|
||||
if self.current_current == current && self.current_max == max {
|
||||
return;
|
||||
}
|
||||
|
@ -260,7 +258,7 @@ impl<'a> NumberDisplay<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, new_value: Option<u32>, obj: &'a ObjectController) {
|
||||
pub fn set_value(&mut self, new_value: Option<u32>, obj: &'a OamManaged) {
|
||||
if self.value == new_value {
|
||||
return;
|
||||
}
|
||||
|
@ -271,7 +269,7 @@ impl<'a> NumberDisplay<'a> {
|
|||
|
||||
if let Some(mut new_value) = new_value {
|
||||
if new_value == 0 {
|
||||
let mut zero_object = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
|
||||
let mut zero_object = obj.object_sprite(SMALL_SPRITES.number(0));
|
||||
zero_object.show().set_position(self.position);
|
||||
|
||||
self.objects.push(zero_object);
|
||||
|
@ -284,7 +282,7 @@ impl<'a> NumberDisplay<'a> {
|
|||
new_value /= 10;
|
||||
|
||||
let mut current_value_obj =
|
||||
obj.object(obj.sprite(SMALL_SPRITES.number(current_value_digit)));
|
||||
obj.object_sprite(SMALL_SPRITES.number(current_value_digit));
|
||||
|
||||
current_value_obj
|
||||
.show()
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
||||
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
|
||||
|
||||
use agb::display::object::ObjectController;
|
||||
use agb::display::object::OamManaged;
|
||||
use agb::display::tiled::{TileFormat, TiledMap, VRamManager};
|
||||
use agb::display::Priority;
|
||||
use agb::interrupt::VBlank;
|
||||
|
@ -90,7 +90,7 @@ pub struct PlayerDice {
|
|||
}
|
||||
|
||||
struct Agb<'a> {
|
||||
obj: ObjectController<'a>,
|
||||
obj: OamManaged<'a>,
|
||||
vblank: VBlank,
|
||||
star_background: StarBackground<'a>,
|
||||
vram: VRamManager,
|
||||
|
@ -104,7 +104,7 @@ pub fn main(mut gba: agb::Gba) -> ! {
|
|||
save::save_high_score(&mut gba.save, 0).expect("Could not reset high score");
|
||||
}
|
||||
|
||||
let gfx = gba.display.object.get();
|
||||
let gfx = gba.display.object.get_managed();
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
|
||||
let (tiled, mut vram) = gba.display.video.tiled0();
|
||||
|
|
167
examples/the-hat-chooses-the-wizard/Cargo.lock
generated
|
@ -16,65 +16,71 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
|||
|
||||
[[package]]
|
||||
name = "agb"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
"bare-metal",
|
||||
"bitflags 2.0.2",
|
||||
"bitflags 2.1.0",
|
||||
"modular-bitfield",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_fixnum"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"asefile",
|
||||
"fontdue",
|
||||
"image",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.8",
|
||||
"toml",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -113,9 +119,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.0.2"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
|
||||
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
|
@ -172,40 +178,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
|
||||
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
|
@ -227,28 +216,12 @@ dependencies = [
|
|||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
|
@ -258,12 +231,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
|
@ -370,9 +337,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.53"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -400,44 +367,35 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.158"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.158"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
|
||||
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.94"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
|
||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -457,9 +415,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.8"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -475,40 +433,6 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
|
@ -526,18 +450,3 @@ name = "version_check"
|
|||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -7,11 +7,18 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { version = "0.14.0", path = "../../agb" }
|
||||
agb = { version = "0.15.0", path = "../../agb" }
|
||||
|
||||
[build-dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
version = "1.0"
|
||||
|
||||
transparent_colour = "2ce8f4"
|
||||
|
||||
[image.thanks_for_playing]
|
||||
filename = "thanks_for_playing.png"
|
||||
tile_size = "8x8"
|
||||
|
||||
[image.splash]
|
||||
filename = "splash.png"
|
||||
tile_size = "8x8"
|
|
@ -1,7 +0,0 @@
|
|||
version = "1.0"
|
||||
|
||||
transparent_colour = "2ce8f4"
|
||||
|
||||
[image.background]
|
||||
filename = "tile_sheet.png"
|
||||
tile_size = "8x8"
|
|
@ -2,7 +2,7 @@ use crate::TAG_MAP;
|
|||
|
||||
use super::{sfx::SfxPlayer, Entity, FixedNumberType, HatState, Level};
|
||||
use agb::{
|
||||
display::object::{ObjectController, Tag},
|
||||
display::object::{OamManaged, Tag},
|
||||
fixnum::Vector2D,
|
||||
};
|
||||
|
||||
|
@ -35,11 +35,11 @@ pub enum EnemyUpdateState {
|
|||
}
|
||||
|
||||
impl<'a> Enemy<'a> {
|
||||
pub fn new_slime(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
pub fn new_slime(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
Enemy::Slime(Slime::new(object, start_pos + (0, 1).into()))
|
||||
}
|
||||
|
||||
pub fn new_snail(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
pub fn new_snail(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
Enemy::Snail(Snail::new(object, start_pos))
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ impl<'a> Enemy<'a> {
|
|||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
level: &Level,
|
||||
player_pos: Vector2D<FixedNumberType>,
|
||||
hat_state: HatState,
|
||||
|
@ -94,7 +94,7 @@ struct EnemyInfo<'a> {
|
|||
|
||||
impl<'a> EnemyInfo<'a> {
|
||||
fn new(
|
||||
object: &'a ObjectController,
|
||||
object: &'a OamManaged,
|
||||
start_pos: Vector2D<FixedNumberType>,
|
||||
collision: Vector2D<u16>,
|
||||
) -> Self {
|
||||
|
@ -135,7 +135,7 @@ pub struct Slime<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Slime<'a> {
|
||||
fn new(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
fn new(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
let slime = Slime {
|
||||
enemy_info: EnemyInfo::new(object, start_pos, (14u16, 14u16).into()),
|
||||
state: SlimeState::Idle,
|
||||
|
@ -146,7 +146,7 @@ impl<'a> Slime<'a> {
|
|||
|
||||
fn update(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
level: &Level,
|
||||
player_pos: Vector2D<FixedNumberType>,
|
||||
hat_state: HatState,
|
||||
|
@ -257,7 +257,7 @@ pub struct Snail<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Snail<'a> {
|
||||
fn new(object: &'a ObjectController, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
fn new(object: &'a OamManaged, start_pos: Vector2D<FixedNumberType>) -> Self {
|
||||
let snail = Snail {
|
||||
enemy_info: EnemyInfo::new(object, start_pos, (16u16, 16u16).into()),
|
||||
state: SnailState::Idle(0),
|
||||
|
@ -272,7 +272,7 @@ impl<'a> Snail<'a> {
|
|||
|
||||
fn update(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
level: &Level,
|
||||
player_pos: Vector2D<FixedNumberType>,
|
||||
hat_state: HatState,
|
||||
|
|
|
@ -8,7 +8,7 @@ extern crate alloc;
|
|||
|
||||
use agb::{
|
||||
display::{
|
||||
object::{Graphics, Object, ObjectController, Tag, TagMap},
|
||||
object::{Graphics, OamManaged, Object, Tag, TagMap},
|
||||
tiled::{
|
||||
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
|
||||
TileSetting, TiledMap, VRamManager,
|
||||
|
@ -101,7 +101,7 @@ mod map_tiles {
|
|||
}
|
||||
}
|
||||
|
||||
agb::include_gfx!("gfx/tile_sheet.toml");
|
||||
agb::include_background_gfx!(tile_sheet, "2ce8f4", background => "gfx/tile_sheet.png");
|
||||
|
||||
const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite");
|
||||
const TAG_MAP: &TagMap = GRAPHICS.tags();
|
||||
|
@ -124,12 +124,11 @@ pub struct Entity<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Entity<'a> {
|
||||
pub fn new(object: &'a ObjectController, collision_mask: Vector2D<u16>) -> Self {
|
||||
let dummy_sprite = object.sprite(WALKING.sprite(0));
|
||||
let mut sprite = object.object(dummy_sprite);
|
||||
sprite.set_priority(Priority::P1);
|
||||
pub fn new(object: &'a OamManaged, collision_mask: Vector2D<u16>) -> Self {
|
||||
let mut dummy_object = object.object_sprite(WALKING.sprite(0));
|
||||
dummy_object.set_priority(Priority::P1);
|
||||
Entity {
|
||||
sprite,
|
||||
sprite: dummy_object,
|
||||
collision_mask,
|
||||
position: (0, 0).into(),
|
||||
velocity: (0, 0).into(),
|
||||
|
@ -348,7 +347,7 @@ fn ping_pong(i: i32, n: i32) -> i32 {
|
|||
}
|
||||
|
||||
impl<'a> Player<'a> {
|
||||
fn new(controller: &'a ObjectController, start_position: Vector2D<FixedNumberType>) -> Self {
|
||||
fn new(controller: &'a OamManaged, start_position: Vector2D<FixedNumberType>) -> Self {
|
||||
let mut wizard = Entity::new(controller, (6_u16, 14_u16).into());
|
||||
let mut hat = Entity::new(controller, (6_u16, 6_u16).into());
|
||||
|
||||
|
@ -382,7 +381,7 @@ impl<'a> Player<'a> {
|
|||
fn update_frame(
|
||||
&mut self,
|
||||
input: &ButtonController,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
timer: i32,
|
||||
level: &Level,
|
||||
enemies: &[enemies::Enemy],
|
||||
|
@ -616,7 +615,7 @@ enum UpdateState {
|
|||
impl<'a, 'b> PlayingLevel<'a, 'b> {
|
||||
fn open_level(
|
||||
level: &'a Level,
|
||||
object_control: &'a ObjectController,
|
||||
object_control: &'a OamManaged,
|
||||
background: &'a mut InfiniteScrolledMap<'b>,
|
||||
foreground: &'a mut InfiniteScrolledMap<'b>,
|
||||
input: ButtonController,
|
||||
|
@ -677,7 +676,7 @@ impl<'a, 'b> PlayingLevel<'a, 'b> {
|
|||
self.player.wizard.sprite.set_priority(Priority::P0);
|
||||
}
|
||||
|
||||
fn dead_update(&mut self, controller: &'a ObjectController) -> bool {
|
||||
fn dead_update(&mut self, controller: &'a OamManaged) -> bool {
|
||||
self.timer += 1;
|
||||
|
||||
let frame = PLAYER_DEATH.animation_sprite(self.timer as usize / 8);
|
||||
|
@ -696,7 +695,7 @@ impl<'a, 'b> PlayingLevel<'a, 'b> {
|
|||
&mut self,
|
||||
sfx_player: &mut SfxPlayer,
|
||||
vram: &mut VRamManager,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
) -> UpdateState {
|
||||
self.timer += 1;
|
||||
self.input.update();
|
||||
|
@ -828,7 +827,7 @@ pub fn main(mut agb: agb::Gba) -> ! {
|
|||
|
||||
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||
|
||||
let object = agb.display.object.get();
|
||||
let object = agb.display.object.get_managed();
|
||||
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
let mut current_level = 0;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use super::sfx::SfxPlayer;
|
||||
use agb::display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager};
|
||||
|
||||
agb::include_gfx!("gfx/splash_screens.toml");
|
||||
agb::include_background_gfx!(splash_screens,
|
||||
splash => "gfx/splash.png",
|
||||
thanks_for_playing => "gfx/thanks_for_playing.png",
|
||||
);
|
||||
|
||||
pub enum SplashScreen {
|
||||
Start,
|
||||
|
|
173
examples/the-purple-night/Cargo.lock
generated
|
@ -16,65 +16,71 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
|||
|
||||
[[package]]
|
||||
name = "agb"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
"bare-metal",
|
||||
"bitflags 2.0.2",
|
||||
"bitflags 2.1.0",
|
||||
"modular-bitfield",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_fixnum"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"asefile",
|
||||
"fontdue",
|
||||
"image",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.8",
|
||||
"toml",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"cfg-if 1.0.0",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -122,9 +128,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.0.2"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
|
||||
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
|
@ -187,11 +193,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
|
||||
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
|
@ -204,32 +210,15 @@ dependencies = [
|
|||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
|
@ -251,22 +240,6 @@ dependencies = [
|
|||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "0.1.27"
|
||||
|
@ -288,12 +261,6 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
|
@ -400,9 +367,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.53"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -428,35 +395,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -476,9 +414,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.8"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -512,40 +450,6 @@ dependencies = [
|
|||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
|
@ -564,21 +468,6 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { path = "../../agb", version = "0.14.0" }
|
||||
agb = { path = "../../agb", version = "0.15.0" }
|
||||
generational-arena = { version = "0.2", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
|
@ -15,8 +15,11 @@ quote = "1.0.10"
|
|||
tiled = { version = "0.9.4", default-features = false }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
|
Before Width: | Height: | Size: 2 KiB |
|
@ -1,7 +0,0 @@
|
|||
version = "1.0"
|
||||
|
||||
transparent_colour = "53269a"
|
||||
|
||||
[image.background]
|
||||
filename = "background.png"
|
||||
tile_size = "8x8"
|
Before Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 9.5 KiB |
|
@ -14,14 +14,14 @@ use alloc::{boxed::Box, vec::Vec};
|
|||
|
||||
use agb::{
|
||||
display::{
|
||||
object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap},
|
||||
object::{Graphics, OamManaged, Object, Sprite, Tag, TagMap},
|
||||
tiled::{
|
||||
InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting,
|
||||
VRamManager,
|
||||
},
|
||||
Priority, HEIGHT, WIDTH,
|
||||
},
|
||||
fixnum::{FixedNum, Rect, Vector2D},
|
||||
fixnum::{num, FixedNum, Rect, Vector2D},
|
||||
input::{Button, ButtonController, Tri},
|
||||
interrupt::VBlank,
|
||||
rng,
|
||||
|
@ -57,7 +57,7 @@ const SWORDLESS_JUMP: &Tag = TAG_MAP.get("jump swordless");
|
|||
const SWORDLESS_ATTACK: &Tag = KNIFE_ATTACK;
|
||||
const SWORDLESS_JUMP_ATTACK: &Tag = KNIFE_JUMP_ATTACK;
|
||||
|
||||
agb::include_gfx!("gfx/background.toml");
|
||||
agb::include_background_gfx!(background, "53269a", background => "gfx/background.aseprite");
|
||||
|
||||
type Number = FixedNum<8>;
|
||||
|
||||
|
@ -159,14 +159,13 @@ struct Entity<'a> {
|
|||
sprite: Object<'a>,
|
||||
position: Vector2D<Number>,
|
||||
velocity: Vector2D<Number>,
|
||||
collision_mask: Rect<u16>,
|
||||
collision_mask: Rect<Number>,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl<'a> Entity<'a> {
|
||||
fn new(object_controller: &'a ObjectController, collision_mask: Rect<u16>) -> Self {
|
||||
let s = object_controller.sprite(LONG_SWORD_IDLE.sprite(0));
|
||||
let mut sprite = object_controller.object(s);
|
||||
fn new(object_controller: &'a OamManaged, collision_mask: Rect<Number>) -> Self {
|
||||
let mut sprite = object_controller.object_sprite(LONG_SWORD_IDLE.sprite(0));
|
||||
sprite.set_priority(Priority::P1);
|
||||
Entity {
|
||||
sprite,
|
||||
|
@ -213,18 +212,7 @@ impl<'a> Entity<'a> {
|
|||
}
|
||||
|
||||
fn collider(&self) -> Rect<Number> {
|
||||
let mut number_collision: Rect<Number> = Rect::new(
|
||||
(
|
||||
self.collision_mask.position.x as i32,
|
||||
self.collision_mask.position.y as i32,
|
||||
)
|
||||
.into(),
|
||||
(
|
||||
self.collision_mask.size.x as i32,
|
||||
self.collision_mask.size.y as i32,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
let mut number_collision = self.collision_mask;
|
||||
number_collision.position =
|
||||
self.position + number_collision.position - number_collision.size / 2;
|
||||
number_collision
|
||||
|
@ -273,11 +261,12 @@ impl<'a> Entity<'a> {
|
|||
(final_distance, has_collided)
|
||||
}
|
||||
|
||||
fn commit_with_fudge(&mut self, offset: Vector2D<Number>, fudge: Vector2D<i32>) {
|
||||
fn commit_with_fudge(&mut self, offset: Vector2D<Number>, fudge: Vector2D<Number>) {
|
||||
if !self.visible {
|
||||
self.sprite.hide();
|
||||
} else {
|
||||
let position = (self.position - offset).floor() + fudge;
|
||||
let position =
|
||||
(self.position - offset + fudge + Vector2D::new(num!(0.5), num!(0.5))).floor();
|
||||
self.sprite.set_position(position - (8, 8).into());
|
||||
if position.x < -8
|
||||
|| position.x > WIDTH + 8
|
||||
|
@ -527,17 +516,14 @@ struct Player<'a> {
|
|||
attack_timer: AttackTimer,
|
||||
damage_cooldown: u16,
|
||||
sword: SwordState,
|
||||
fudge_factor: Vector2D<i32>,
|
||||
fudge_factor: Vector2D<Number>,
|
||||
hurtbox: Option<Rect<Number>>,
|
||||
controllable: bool,
|
||||
}
|
||||
|
||||
impl<'a> Player<'a> {
|
||||
fn new(object_controller: &'a ObjectController<'a>) -> Player {
|
||||
let mut entity = Entity::new(
|
||||
object_controller,
|
||||
Rect::new((0_u16, 0_u16).into(), (4_u16, 12_u16).into()),
|
||||
);
|
||||
fn new(object_controller: &'a OamManaged<'_>) -> Player<'a> {
|
||||
let mut entity = Entity::new(object_controller, Rect::new((0, 1).into(), (5, 10).into()));
|
||||
let s = object_controller.sprite(LONG_SWORD_IDLE.sprite(0));
|
||||
entity.sprite.set_sprite(s);
|
||||
entity.sprite.show();
|
||||
|
@ -559,7 +545,7 @@ impl<'a> Player<'a> {
|
|||
|
||||
fn update(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
buttons: &ButtonController,
|
||||
level: &Level,
|
||||
sfx: &mut sfx::Sfx,
|
||||
|
@ -613,7 +599,7 @@ impl<'a> Player<'a> {
|
|||
AttackTimer::Attack(a) => {
|
||||
*a -= 1;
|
||||
let frame = self.sword.attack_frame(*a);
|
||||
self.fudge_factor.x = self.sword.fudge(frame) * self.facing as i32;
|
||||
self.fudge_factor.x = (self.sword.fudge(frame) * self.facing as i32).into();
|
||||
let tag = self.sword.attack_tag();
|
||||
let sprite = controller.sprite(tag.animation_sprite(frame as usize));
|
||||
self.entity.sprite.set_sprite(sprite);
|
||||
|
@ -627,7 +613,7 @@ impl<'a> Player<'a> {
|
|||
AttackTimer::Cooldown(a) => {
|
||||
*a -= 1;
|
||||
let frame = self.sword.hold_frame();
|
||||
self.fudge_factor.x = self.sword.fudge(frame) * self.facing as i32;
|
||||
self.fudge_factor.x = (self.sword.fudge(frame) * self.facing as i32).into();
|
||||
let tag = self.sword.attack_tag();
|
||||
let sprite = controller.sprite(tag.animation_sprite(frame as usize));
|
||||
self.entity.sprite.set_sprite(sprite);
|
||||
|
@ -695,6 +681,8 @@ impl<'a> Player<'a> {
|
|||
let gravity = gravity / 16;
|
||||
self.entity.velocity.y += gravity;
|
||||
|
||||
self.fudge_factor.x -= num!(1.5) * (self.facing as i32);
|
||||
|
||||
let fudge_number = (self.fudge_factor.x, self.fudge_factor.y).into();
|
||||
|
||||
// convert the hurtbox to a location in the game
|
||||
|
@ -811,7 +799,7 @@ impl BatData {
|
|||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
entity: &mut Entity<'a>,
|
||||
player: &Player,
|
||||
level: &Level,
|
||||
|
@ -944,7 +932,7 @@ impl SlimeData {
|
|||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
entity: &mut Entity<'a>,
|
||||
player: &Player,
|
||||
level: &Level,
|
||||
|
@ -1073,7 +1061,7 @@ impl MiniFlameData {
|
|||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
entity: &mut Entity<'a>,
|
||||
player: &Player,
|
||||
_level: &Level,
|
||||
|
@ -1202,7 +1190,7 @@ impl EmuData {
|
|||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
entity: &mut Entity<'a>,
|
||||
player: &Player,
|
||||
level: &Level,
|
||||
|
@ -1351,12 +1339,12 @@ enum UpdateInstruction {
|
|||
}
|
||||
|
||||
impl EnemyData {
|
||||
fn collision_mask(&self) -> Rect<u16> {
|
||||
fn collision_mask(&self) -> Rect<Number> {
|
||||
match self {
|
||||
EnemyData::Slime(_) => Rect::new((0u16, 0u16).into(), (4u16, 11u16).into()),
|
||||
EnemyData::Bat(_) => Rect::new((0u16, 0u16).into(), (12u16, 4u16).into()),
|
||||
EnemyData::MiniFlame(_) => Rect::new((0u16, 0u16).into(), (12u16, 12u16).into()),
|
||||
EnemyData::Emu(_) => Rect::new((0u16, 0u16).into(), (7u16, 11u16).into()),
|
||||
EnemyData::Slime(_) => Rect::new((0.into(), num!(1.5)).into(), (4, 11).into()),
|
||||
EnemyData::Bat(_) => Rect::new((0, 0).into(), (12, 4).into()),
|
||||
EnemyData::MiniFlame(_) => Rect::new((0, 0).into(), (12, 12).into()),
|
||||
EnemyData::Emu(_) => Rect::new((0, 0).into(), (7, 11).into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1375,7 +1363,7 @@ impl EnemyData {
|
|||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
entity: &mut Entity<'a>,
|
||||
player: &Player,
|
||||
level: &Level,
|
||||
|
@ -1396,7 +1384,7 @@ struct Enemy<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Enemy<'a> {
|
||||
fn new(object_controller: &'a ObjectController, enemy_data: EnemyData) -> Self {
|
||||
fn new(object_controller: &'a OamManaged, enemy_data: EnemyData) -> Self {
|
||||
let mut entity = Entity::new(object_controller, enemy_data.collision_mask());
|
||||
|
||||
let sprite = enemy_data.sprite();
|
||||
|
@ -1410,7 +1398,7 @@ impl<'a> Enemy<'a> {
|
|||
|
||||
fn update(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
player: &Player,
|
||||
level: &Level,
|
||||
sfx: &mut sfx::Sfx,
|
||||
|
@ -1441,7 +1429,7 @@ impl ParticleData {
|
|||
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
entity: &mut Entity<'a>,
|
||||
player: &Player,
|
||||
_level: &Level,
|
||||
|
@ -1529,14 +1517,11 @@ struct Particle<'a> {
|
|||
|
||||
impl<'a> Particle<'a> {
|
||||
fn new(
|
||||
object_controller: &'a ObjectController,
|
||||
object_controller: &'a OamManaged,
|
||||
particle_data: ParticleData,
|
||||
position: Vector2D<Number>,
|
||||
) -> Self {
|
||||
let mut entity = Entity::new(
|
||||
object_controller,
|
||||
Rect::new((0u16, 0u16).into(), (0u16, 0u16).into()),
|
||||
);
|
||||
let mut entity = Entity::new(object_controller, Rect::new((0, 0).into(), (0, 0).into()));
|
||||
|
||||
entity.position = position;
|
||||
|
||||
|
@ -1548,7 +1533,7 @@ impl<'a> Particle<'a> {
|
|||
|
||||
fn update(
|
||||
&mut self,
|
||||
controller: &'a ObjectController,
|
||||
controller: &'a OamManaged,
|
||||
player: &Player,
|
||||
level: &Level,
|
||||
) -> UpdateInstruction {
|
||||
|
@ -1575,7 +1560,7 @@ impl<'a> BossState<'a> {
|
|||
fn update(
|
||||
&mut self,
|
||||
enemies: &mut Arena<Enemy<'a>>,
|
||||
object_controller: &'a ObjectController,
|
||||
object_controller: &'a OamManaged,
|
||||
player: &Player,
|
||||
sfx: &mut sfx::Sfx,
|
||||
) -> BossInstruction {
|
||||
|
@ -1610,11 +1595,8 @@ struct FollowingBoss<'a> {
|
|||
}
|
||||
|
||||
impl<'a> FollowingBoss<'a> {
|
||||
fn new(object_controller: &'a ObjectController, position: Vector2D<Number>) -> Self {
|
||||
let mut entity = Entity::new(
|
||||
object_controller,
|
||||
Rect::new((0_u16, 0_u16).into(), (0_u16, 0_u16).into()),
|
||||
);
|
||||
fn new(object_controller: &'a OamManaged, position: Vector2D<Number>) -> Self {
|
||||
let mut entity = Entity::new(object_controller, Rect::new((0, 0).into(), (0, 0).into()));
|
||||
entity.position = position;
|
||||
|
||||
Self {
|
||||
|
@ -1625,7 +1607,7 @@ impl<'a> FollowingBoss<'a> {
|
|||
gone: false,
|
||||
}
|
||||
}
|
||||
fn update(&mut self, controller: &'a ObjectController, player: &Player) {
|
||||
fn update(&mut self, controller: &'a OamManaged, player: &Player) {
|
||||
let difference = player.entity.position - self.entity.position;
|
||||
self.timer += 1;
|
||||
|
||||
|
@ -1694,11 +1676,8 @@ enum BossInstruction {
|
|||
}
|
||||
|
||||
impl<'a> Boss<'a> {
|
||||
fn new(object_controller: &'a ObjectController, screen_coords: Vector2D<Number>) -> Self {
|
||||
let mut entity = Entity::new(
|
||||
object_controller,
|
||||
Rect::new((0_u16, 0_u16).into(), (28_u16, 28_u16).into()),
|
||||
);
|
||||
fn new(object_controller: &'a OamManaged, screen_coords: Vector2D<Number>) -> Self {
|
||||
let mut entity = Entity::new(object_controller, Rect::new((0, 0).into(), (28, 28).into()));
|
||||
entity.position = screen_coords + (144, 136).into();
|
||||
Self {
|
||||
entity,
|
||||
|
@ -1713,7 +1692,7 @@ impl<'a> Boss<'a> {
|
|||
fn update(
|
||||
&mut self,
|
||||
enemies: &mut Arena<Enemy<'a>>,
|
||||
object_controller: &'a ObjectController,
|
||||
object_controller: &'a OamManaged,
|
||||
player: &Player,
|
||||
sfx: &mut sfx::Sfx,
|
||||
) -> BossInstruction {
|
||||
|
@ -1817,7 +1796,7 @@ impl<'a> Boss<'a> {
|
|||
self.entity
|
||||
.commit_with_size(offset + shake, (32, 32).into());
|
||||
}
|
||||
fn explode(&self, enemies: &mut Arena<Enemy<'a>>, object_controller: &'a ObjectController) {
|
||||
fn explode(&self, enemies: &mut Arena<Enemy<'a>>, object_controller: &'a OamManaged) {
|
||||
for _ in 0..(6 - self.health) {
|
||||
let x_offset: Number = Number::from_raw(rng::gen()).rem_euclid(2.into()) - 1;
|
||||
let y_offset: Number = Number::from_raw(rng::gen()).rem_euclid(2.into()) - 1;
|
||||
|
@ -1891,7 +1870,7 @@ impl<'a> Game<'a> {
|
|||
|
||||
fn advance_frame(
|
||||
&mut self,
|
||||
object_controller: &'a ObjectController,
|
||||
object_controller: &'a OamManaged,
|
||||
vram: &mut VRamManager,
|
||||
sfx: &mut sfx::Sfx,
|
||||
) -> GameStatus {
|
||||
|
@ -1986,6 +1965,8 @@ impl<'a> Game<'a> {
|
|||
self.shake_time -= 1;
|
||||
}
|
||||
|
||||
let this_frame_offset = this_frame_offset.floor().into();
|
||||
|
||||
self.input.update();
|
||||
if let UpdateInstruction::CreateParticle(data, position) =
|
||||
self.player
|
||||
|
@ -1996,7 +1977,7 @@ impl<'a> Game<'a> {
|
|||
self.particles.insert(new_particle);
|
||||
}
|
||||
|
||||
let mut remove = Vec::with_capacity(10);
|
||||
let mut remove = Vec::new();
|
||||
for (idx, enemy) in self.enemies.iter_mut() {
|
||||
if enemy.entity.position.x < self.offset.x - 8 {
|
||||
remove.push(idx);
|
||||
|
@ -2086,10 +2067,6 @@ impl<'a> Game<'a> {
|
|||
.commit_with_fudge(this_frame_offset, (0, 0).into());
|
||||
}
|
||||
|
||||
self.level.background.commit(vram);
|
||||
self.level.foreground.commit(vram);
|
||||
self.level.clouds.commit(vram);
|
||||
|
||||
for i in remove {
|
||||
self.particles.remove(i);
|
||||
}
|
||||
|
@ -2105,7 +2082,7 @@ impl<'a> Game<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_enemies(&mut self, object_controller: &'a ObjectController) {
|
||||
fn load_enemies(&mut self, object_controller: &'a OamManaged) {
|
||||
if self.slime_load < self.level.slime_spawns.len() {
|
||||
for (idx, slime_spawn) in self
|
||||
.level
|
||||
|
@ -2175,7 +2152,7 @@ impl<'a> Game<'a> {
|
|||
vram.set_background_palettes(&modified_palettes);
|
||||
}
|
||||
|
||||
fn new(object: &'a ObjectController, level: Level<'a>, start_at_boss: bool) -> Self {
|
||||
fn new(object: &'a OamManaged<'a>, level: Level<'a>, start_at_boss: bool) -> Self {
|
||||
let mut player = Player::new(object);
|
||||
let mut offset = (8, 8).into();
|
||||
if start_at_boss {
|
||||
|
@ -2215,15 +2192,12 @@ fn game_with_level(gba: &mut agb::Gba) {
|
|||
|
||||
let mut start_at_boss = false;
|
||||
|
||||
loop {
|
||||
let (background, mut vram) = gba.display.video.tiled0();
|
||||
|
||||
vram.set_background_palettes(background::PALETTES);
|
||||
|
||||
let tileset = TileSet::new(background::background.tiles, TileFormat::FourBpp);
|
||||
let object = gba.display.object.get_managed();
|
||||
|
||||
let object = gba.display.object.get();
|
||||
|
||||
loop {
|
||||
let backdrop = InfiniteScrolledMap::new(
|
||||
background.background(
|
||||
Priority::P2,
|
||||
|
@ -2293,6 +2267,9 @@ fn game_with_level(gba: &mut agb::Gba) {
|
|||
start_at_boss = loop {
|
||||
sfx.frame();
|
||||
vblank.wait_for_vblank();
|
||||
game.level.background.commit(&mut vram);
|
||||
game.level.foreground.commit(&mut vram);
|
||||
game.level.clouds.commit(&mut vram);
|
||||
object.commit();
|
||||
match game.advance_frame(&object, &mut vram, &mut sfx) {
|
||||
GameStatus::Continue => {}
|
||||
|
|
10
justfile
|
@ -14,6 +14,7 @@ clippy:
|
|||
test:
|
||||
just _test-debug agb
|
||||
just _test-debug agb-fixnum
|
||||
just _test-debug agb-hashmap
|
||||
just _test-debug-arm agb
|
||||
just _test-debug tools
|
||||
|
||||
|
@ -27,6 +28,7 @@ doctest-agb:
|
|||
check-docs:
|
||||
(cd agb && cargo doc --target=thumbv6m-none-eabi --no-deps)
|
||||
just _build_docs agb-fixnum
|
||||
just _build_docs agb-hashmap
|
||||
|
||||
_build_docs crate:
|
||||
(cd "{{crate}}" && cargo doc --no-deps)
|
||||
|
@ -59,13 +61,14 @@ check-linker-script-consistency:
|
|||
find -type f -name gba.ld -print0 | xargs -0 -n1 cmp -- agb/gba.ld
|
||||
find -type f -name gba_mb.ld -print0 | xargs -0 -n1 cmp -- agb/gba_mb.ld
|
||||
|
||||
ci: check-linker-script-consistency build-debug clippy fmt-check test build-release test-release doctest-agb build-roms build-book check-docs
|
||||
ci: check-linker-script-consistency build-debug clippy fmt-check test miri build-release test-release doctest-agb build-roms build-book check-docs
|
||||
|
||||
build-roms:
|
||||
just _build-rom "examples/the-purple-night" "PURPLENIGHT"
|
||||
just _build-rom "examples/the-hat-chooses-the-wizard" "HATWIZARD"
|
||||
just _build-rom "examples/hyperspace-roll" "HYPERSPACE"
|
||||
just _build-rom "examples/combo" "AGBJAMS"
|
||||
just _build-rom "examples/amplitude" "AMPLITUDE"
|
||||
just _build-rom "examples/combo" "AGBGAMES"
|
||||
|
||||
just _build-rom "book/games/pong" "PONG"
|
||||
|
||||
|
@ -85,6 +88,9 @@ publish *args: (_run-tool "publish" args)
|
|||
|
||||
release +args: (_run-tool "release" args)
|
||||
|
||||
miri:
|
||||
(cd agb-hashmap && cargo miri test)
|
||||
|
||||
_run-tool +tool:
|
||||
(cd tools && cargo build)
|
||||
"$CARGO_TARGET_DIR/debug/tools" {{tool}}
|
||||
|
|