use bilge::prelude::*;
use bytemuck::{Pod, Zeroable};
use paste::paste;
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::ops::Range;
pub mod alloc;
pub use tic80_sys::{Flip, Rotate};
pub use tic80_sys::FRAMEBUFFER as tic80_start;
pub mod prelude {
pub use super::{cart, trace, trace_color, Bpp, Cart, GamePadTrait, Tic80};
pub use cstr::cstr;
}
pub trait Cart {
fn boot(tic80: &mut Tic80) -> Self;
fn menu(&mut self, _tic80: &mut Tic80, _idx: u32) {}
fn tic(&mut self, _tic80: &mut Tic80);
fn scn(&mut self, _tic80: &mut Tic80, _scan: u32) {}
}
#[macro_export]
macro_rules! trace_color {
($color: expr, $text: literal) => {
{
use $crate::Tic80;
use std::ffi::CStr;
Tic80::trace($crate::prelude::cstr!($text), $color)
}
};
($color: expr, $text:literal, $($tt:tt)*)=> {
{
use $crate::Tic80;
use std::ffi::CStr;
let s = format!(concat!($text, "\0"), $($tt)*);
let cstr = CStr::from_bytes_with_nul(s.as_bytes()).unwrap();
Tic80::trace(cstr, $color)
}
};
}
#[macro_export]
macro_rules! trace {
($text:literal) => {
$crate::trace_color!(None, $text);
};
($text:literal, $($tt:tt)*)=> {
$crate::trace_color!(None, $text, $($tt)*);
};
}
#[macro_export]
macro_rules! cart {
($cart:ident) => {
mod cart_init {
use super::$cart;
use std::cell::OnceCell;
use $crate::{Cart, Tic80};
static mut CART: OnceCell<$cart> = OnceCell::new();
#[export_name = "TIC"]
pub fn tic() {
let mut tic80 = unsafe { Tic80::from_offset($crate::tic80_start as usize) };
unsafe { &mut CART }.get_mut().unwrap().tic(&mut tic80);
}
#[export_name = "BOOT"]
pub fn boot() {
let mut tic80 = unsafe { Tic80::from_offset($crate::tic80_start as usize) };
$crate::trace!("BOOT");
unsafe { &mut CART }.get_or_init(|| <$cart>::boot(&mut tic80));
}
#[export_name = "SCN"]
pub fn scn(scan: u32) {
let mut tic80 = unsafe { Tic80::from_offset($crate::tic80_start as usize) };
unsafe { &mut CART }
.get_mut()
.unwrap()
.scn(&mut tic80, scan);
}
#[export_name = "MENU"]
pub fn menu(idx: u32) {
let mut tic80 = unsafe { Tic80::from_offset($crate::tic80_start as usize) };
$crate::trace!("MENU {}", idx);
unsafe { &mut CART }
.get_mut()
.unwrap()
.menu(&mut tic80, idx);
}
}
};
}
#[bitsize(4)]
#[derive(Copy, Clone, FromBits)]
pub struct BlitSegmentBpp4 {
pub fg: u1,
reserved: u3,
}
impl BlitSegmentBpp4 {
pub fn page(&self) -> u1 {
u1::new(0)
}
}
#[bitsize(4)]
#[derive(Copy, Clone, FromBits)]
pub struct BlitSegmentBpp2 {
pub page: u1,
pub fg: u1,
reserved: u2,
}
#[bitsize(4)]
#[derive(Copy, Clone, FromBits)]
pub struct BlitSegmentBpp1 {
pub page: u2,
pub fg: u1,
reserved: u1,
}
#[derive(Copy, Clone)]
pub enum BlitSegment {
Reserved(u4),
Bpp1(BlitSegmentBpp1),
Bpp2(BlitSegmentBpp2),
Bpp4(BlitSegmentBpp4),
}
impl Default for BlitSegment {
fn default() -> Self {
BlitSegment::from(u4::new(0b0010))
}
}
impl BlitSegment {
fn value(&self) -> u4 {
match self {
Self::Reserved(v) => *v,
Self::Bpp1(v) => (*v).into(),
Self::Bpp2(v) => (*v).into(),
Self::Bpp4(v) => (*v).into(),
}
}
fn from(bpp: u4) -> BlitSegment {
if bpp.value() & 0b1110 == 0b0010 {
Self::Bpp4(BlitSegmentBpp4::from(bpp))
} else if bpp.value() & 0b1100 == 0b0100 {
Self::Bpp2(BlitSegmentBpp2::from(bpp))
} else if bpp.value() & 0b1000 == 0b1000 {
Self::Bpp1(BlitSegmentBpp1::from(bpp))
} else {
Self::Reserved(bpp)
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct SpriteIdx<T, V> {
pub idx: V,
_phantom: PhantomData<T>,
}
pub type SpriteIdxBpp4 = SpriteIdx<Bpp4, u9>;
pub type SpriteIdxBpp2 = SpriteIdx<Bpp2, u10>;
pub type SpriteIdxBpp1 = SpriteIdx<Bpp1, u11>;
impl<T, V> TryFrom<u16> for SpriteIdx<T, V>
where
T: Bpp,
V: Number,
{
type Error = ();
fn try_from(v: u16) -> Result<Self, Self::Error> {
Ok(Self {
idx: V::try_new(v.try_into().map_err(|_| ()).unwrap()).unwrap(),
_phantom: PhantomData,
})
}
}
impl SpriteIdxBpp4 {
pub fn bpp4<T>(v: T) -> Self
where
T: TryInto<u16> + std::fmt::Debug,
{
Self {
idx: u9::try_new(v.try_into().map_err(|_| ()).unwrap()).unwrap(),
_phantom: PhantomData,
}
}
}
impl SpriteIdxBpp2 {
pub fn bpp2<T>(v: T) -> Self
where
T: TryInto<u16> + std::fmt::Debug,
{
Self {
idx: u10::try_new(v.try_into().map_err(|_| ()).unwrap()).unwrap(),
_phantom: PhantomData,
}
}
}
impl SpriteIdxBpp1 {
pub fn bpp1<T>(v: T) -> Self
where
T: TryInto<u16> + std::fmt::Debug,
{
Self {
idx: u11::try_new(v.try_into().map_err(|_| ()).unwrap()).unwrap(),
_phantom: PhantomData,
}
}
}
impl<T, V> SpriteIdx<T, V>
where
T: Bpp<SpriteIdxSize = V>,
V: Copy + bilge::prelude::Number,
u16: From<<V as bilge::prelude::Number>::UnderlyingType>,
{
fn value(&self) -> u16 {
u16::try_from(self.idx.value()).unwrap()
}
}
pub type TileBpp1 = Tile<<Bpp1 as Bpp>::Tile, Bpp1>;
pub type TileBpp2 = Tile<<Bpp2 as Bpp>::Tile, Bpp2>;
pub type TileBpp4 = Tile<<Bpp4 as Bpp>::Tile, Bpp4>;
pub trait Bpp: std::fmt::Debug {
type SpriteIdxSize: Number;
type PaletteIdxSize: Number;
type Tile: AsMut<[u8]> + AsRef<[u8]> + Default + std::fmt::Debug;
const FG_START: Self::SpriteIdxSize;
fn blit_segment() -> BlitSegment;
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Bpp1;
impl Bpp for Bpp1 {
type SpriteIdxSize = u11;
type PaletteIdxSize = u1;
const FG_START: u11 = u11::new(1024);
type Tile = [u8; 8];
fn blit_segment() -> BlitSegment {
BlitSegment::from(u4::new(0b1000))
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Bpp2;
impl Bpp for Bpp2 {
type SpriteIdxSize = u10;
type PaletteIdxSize = u2;
type Tile = [u8; 16];
const FG_START: u10 = u10::new(512);
fn blit_segment() -> BlitSegment {
BlitSegment::from(u4::new(0b0100))
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Bpp4;
impl Bpp for Bpp4 {
type SpriteIdxSize = u9;
type PaletteIdxSize = u4;
type Tile = [u8; 32];
const FG_START: u9 = u9::new(256);
fn blit_segment() -> BlitSegment {
BlitSegment::from(u4::new(0b0010))
}
}
#[repr(transparent)]
#[bitsize(4)]
#[derive(FromBits, Copy, Clone, DebugBits, PartialEq)]
pub struct PaletteIdx(pub u4);
#[bitsize(8)]
#[derive(FromBits, Copy, Clone, DebugBits, PartialEq)]
pub struct PalettePair {
pub low: PaletteIdx,
pub high: PaletteIdx,
}
#[repr(transparent)]
#[bitsize(5)]
#[derive(FromBits, Copy, Clone, DebugBits)]
pub struct ButtonIdx(pub u5);
#[bitsize(8)]
#[derive(FromBits, Debug, PartialEq)]
pub enum Key {
A = 1,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Num0,
Num1,
Num2,
Num3,
Num4,
Num5,
Num6,
Num7,
Num8,
Num9,
Minus,
Equals,
LeftBrack,
RightBracket,
Backslash,
Semicolon,
Apostrophe,
Grave,
Gomma,
Period,
Slash,
Space,
Tab,
Return,
Backspace,
Delete,
Insert,
PageUp,
PageDown,
Home,
End,
Up,
Down,
Left,
Right,
CapsLock,
Ctrl,
Shift,
Alt,
Esc,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
NumPad0,
NumPad1,
NumPad2,
NumPad3,
NumPad4,
NumPad5,
NumPad6,
NumPad7,
NumPad8,
NumPad9,
NumPadPlus,
NumPadMinus,
NumPadMultiply,
NumPadDivide,
NumPadEnter,
NumPadPeriod,
#[fallback]
Reserved,
}
#[repr(transparent)]
#[bitsize(8)]
#[derive(FromBits, Copy, Clone, DebugBits)]
pub struct BlitRegister {
pub blit: u4,
reserved: u4,
}
#[bitsize(8)]
#[derive(Copy, Clone, PartialEq, FromBits)]
pub struct GamePadButtons {
pub up: bool,
pub down: bool,
pub left: bool,
pub right: bool,
pub a: bool,
pub b: bool,
pub x: bool,
pub y: bool,
}
impl std::ops::BitOr<Button> for GamePadButtons {
type Output = GamePadButtons;
fn bitor(self, rhs: Button) -> Self::Output {
GamePadButtons::from(self.value | 1 << rhs as u32)
}
}
impl std::ops::BitAnd<Button> for GamePadButtons {
type Output = GamePadButtons;
fn bitand(self, rhs: Button) -> Self::Output {
GamePadButtons::from(self.value & (1 << rhs as u32))
}
}
impl std::ops::BitOr<Button> for Button {
type Output = GamePadButtons;
fn bitor(self, rhs: Button) -> Self::Output {
GamePadButtons::from(0) | self | rhs
}
}
impl GamePadButtons {
pub fn button(&self, b: Button) -> bool {
match b {
Button::Up => self.up(),
Button::Down => self.down(),
Button::Left => self.left(),
Button::Right => self.right(),
Button::A => self.a(),
Button::B => self.b(),
Button::X => self.x(),
Button::Y => self.y(),
}
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct Ticks(pub u64);
impl From<Ticks> for Duration {
fn from(t: Ticks) -> Duration {
Duration::from_micros(t.0 * 16_666)
}
}
impl From<Duration> for Ticks {
fn from(t: Duration) -> Ticks {
Ticks(u64::try_from(t.as_micros() / 16_666).unwrap())
}
}
pub struct GamePad<const PLAYER: u8> {
pub buttons: AddressMem<GamePadButtons>,
}
use std::time::Duration;
impl GamePadButtons {
pub fn just_pressed(
&mut self,
b: ButtonIdx,
hold: Option<Ticks>,
period: Option<Ticks>,
) -> bool {
unsafe {
tic80_sys::sys::btnp(
u8::from(b.value).into(),
hold.map(|x| i32::try_from(x.0).unwrap()).unwrap_or(-1),
period.map(|x| i32::try_from(x.0).unwrap()).unwrap_or(-1),
)
}
}
}
#[enum_dispatch]
pub enum GamePadEnum {
GamePad0(GamePad<0>),
GamePad1(GamePad<1>),
GamePad2(GamePad<2>),
GamePad3(GamePad<3>),
}
use enum_dispatch::enum_dispatch;
#[enum_dispatch(GamePadEnum)]
pub trait GamePadTrait {
fn buttons(&self) -> GamePadButtons;
fn all_pressed(&self, buttons: GamePadButtons) -> bool {
(self.buttons().value & buttons.value) == buttons.value
}
fn any_pressed(&self, buttons: GamePadButtons) -> bool {
(self.buttons().value & buttons.value) != 0
}
fn is_any_pressed(&self) -> bool {
self.buttons().value != 0
}
fn just_pressed(&mut self, b: Button, hold: Option<Ticks>, period: Option<Ticks>) -> bool {
self.buttons()
.just_pressed(self.button_idx(b), hold, period)
}
fn button_idx(&self, b: Button) -> ButtonIdx;
}
impl<const PLAYER: u8> GamePadTrait for GamePad<PLAYER> {
fn buttons(&self) -> GamePadButtons {
*self.buttons
}
fn button_idx(&self, b: Button) -> ButtonIdx {
ButtonIdx::from(u5::new((PLAYER << 3) + b as u8))
}
}
#[derive(Copy, Clone, Debug)]
pub struct MousePoint(Point<u8>);
impl<T> From<MousePoint> for Point<T>
where
T: From<u8>,
{
fn from(pt: MousePoint) -> Point<T> {
Point {
x: pt.0.x.into(),
y: pt.0.y.into(),
}
}
}
#[bitsize(128)]
#[derive(FromBits)]
struct WaveForm([u4; 32]);
#[bitsize(16)]
#[derive(FromBits)]
pub struct SoundMeta {
freq: u12,
volume: u4,
}
#[repr(packed)]
#[repr(C)]
pub struct SoundRegister {
meta: SoundMeta,
waveform: WaveForm,
}
#[bitsize(16)]
#[derive(FromBits, DebugBits)]
pub struct MouseBits {
pub left: bool,
pub middle: bool,
pub right: bool,
internal_scroll_h: u6,
internal_scroll_v: u6,
reserved: u1,
}
#[derive(Debug)]
#[repr(C)]
pub struct Mouse {
internal_pos: MousePoint,
bits: MouseBits,
}
impl Mouse {
pub fn scroll_h(&self) -> i8 {
self.bits.internal_scroll_h().sign_extend()
}
pub fn scroll_v(&self) -> i8 {
self.bits.internal_scroll_v().sign_extend()
}
pub fn screen_pos(&self) -> Option<Point<u8>> {
let border_width_x = 8u8;
let border_height_y = 4u8;
self.raw_pos()
.filter(|pos| {
(border_width_x..u8::try_from(tic80_sys::WIDTH).unwrap() + border_width_x)
.contains(&pos.x)
&& (border_height_y..u8::try_from(tic80_sys::HEIGHT).unwrap() + border_height_y)
.contains(&pos.y)
})
.map(|pos| Point {
x: pos.x - border_width_x,
y: pos.y - border_height_y,
})
}
pub fn raw_pos(&self) -> Option<Point<u8>> {
let pos = &self.internal_pos;
let invalid_pos = Point {
x: u8::MAX,
y: u8::MAX,
};
if pos.0 == invalid_pos {
None
} else {
Some(pos.0)
}
}
}
pub trait SignExtend {
type Output;
fn sign_extend(&self) -> Self::Output;
}
impl SignExtend for u6 {
type Output = i8;
fn sign_extend(&self) -> Self::Output {
let invbits = Self::Output::BITS - Self::BITS as u32;
(self.value() as Self::Output)
.wrapping_shl(invbits)
.wrapping_shr(invbits)
}
}
#[repr(u8)]
#[derive(Copy, Clone)]
pub enum Button {
Up,
Down,
Left,
Right,
A,
B,
X,
Y,
}
pub struct Block([PixelPair; 8 * 8 / 2]);
pub struct PixelBlocks {}
macro_rules! gamepads {
($($id:literal ),*) => {
paste! {
[
$(
GamePadEnum::
[< GamePad $id >]
(GamePad {
buttons: unsafe {
AddressMem::new(tic80_sys::GAMEPADS.cast::<GamePadButtons>().add($id).cast())
},
}),
)*
]
}
}
}
impl Tic80 {
pub unsafe fn from_offset(offset: usize) -> Self {
Tic80 {
display: Display {
vram: Vram {
screen: Screen::from_offset(offset),
palette: unsafe { AddressMem::new(tic80_sys::PALETTE.byte_add(offset).cast()) },
palette_map: unsafe {
AddressMem::new(tic80_sys::PALETTE_MAP.byte_add(offset).cast())
},
border_color: unsafe {
AddressMem::new(tic80_sys::BORDER_COLOR.byte_add(offset))
},
mouse_cursor: unsafe {
AddressMem::new(tic80_sys::MOUSE_CURSOR.byte_add(offset))
},
screen_offset: unsafe {
AddressMem::new(tic80_sys::SCREEN_OFFSET_X.byte_add(offset))
},
blit_segment: unsafe {
AddressMem::new(tic80_sys::BLIT_SEGMENT.byte_add(offset))
},
},
sprite_tiles: unsafe { AddressMem::new(tic80_sys::TILES.byte_add(offset).cast()) },
map: unsafe { AddressMem::new(tic80_sys::MAP.byte_add(offset).cast()) },
},
gamepads: gamepads! {
0,1,2,3
},
keyboard: Keyboard {
real_keyboard: unsafe {
AddressMem::new(tic80_sys::KEYBOARD.byte_add(offset).cast())
},
},
mouse: unsafe { AddressMem::new(tic80_sys::MOUSE.byte_add(offset).cast()) },
sound: unsafe { AddressMem::new(tic80_sys::SOUND_REGISTERS.byte_add(offset).cast()) },
sfx: unsafe { AddressMem::new(tic80_sys::SFX.byte_add(offset).cast()) },
music_patterns: unsafe {
AddressMem::new(tic80_sys::MUSIC_PATTERNS.byte_add(offset).cast())
},
persistent_mem: unsafe {
AddressMem::new(tic80_sys::PERSISTENT_RAM.byte_add(offset).cast())
},
sprite_flags: unsafe {
AddressMem::new(tic80_sys::SPRITE_FLAGS.byte_add(offset).cast())
},
}
}
pub fn trace<C: AsRef<CStr>>(text: C, color: Option<u8>) {
unsafe { tic80_sys::sys::trace(text.as_ref().as_ptr().cast(), color.unwrap_or(15)) }
}
pub fn trace_str<S: AsRef<str>>(text: S, color: Option<u8>) {
if let Ok(cstr) = CStr::from_bytes_with_nul(text.as_ref().as_bytes()) {
Self::trace(cstr, color)
} else {
Self::trace(CString::new(text.as_ref()).unwrap(), color)
}
}
}
pub struct DrawBuilder {
clip_region: Option<(Range<i32>, Range<i32>)>,
clear: Option<Option<PaletteIdx>>,
vbank: VramBank,
}
impl Default for DrawBuilder {
fn default() -> Self {
Self {
clip_region: None,
clear: None,
vbank: VramBank::Zero,
}
}
}
impl DrawBuilder {
pub fn with_clip(&mut self, clip_region: (Range<i32>, Range<i32>)) -> &mut Self {
self.clip_region = Some(clip_region);
self
}
pub fn with_bank(&mut self, bank: VramBank) -> &mut Self {
self.vbank = bank;
self
}
pub fn with_clear(&mut self, clear: Option<PaletteIdx>) -> &mut Self {
self.clear = Some(clear);
self
}
pub fn build<'a>(&self, display: &'a mut Display) -> Drawer<'a> {
if let Some(clear) = self.clear {
display.vram.clear(clear);
}
display.clip(self.clip_region.clone());
display.vram.switch_bank(self.vbank);
Drawer { display }
}
}
pub trait Drawable {
type Return;
fn draw(&self, vram: &mut Display) -> Self::Return;
}
impl<T> Drawable for &mut T
where
T: Drawable,
{
type Return = T::Return;
fn draw(&self, vram: &mut Display) -> Self::Return {
<T as Drawable>::draw(self, vram)
}
}
impl<T> Drawable for &T
where
T: Drawable,
{
type Return = T::Return;
fn draw(&self, vram: &mut Display) -> Self::Return {
<T as Drawable>::draw(self, vram)
}
}
pub struct Drawer<'a> {
pub display: &'a mut Display,
}
impl<'a> Drawer<'a> {
pub fn draw<D: Drawable>(&mut self, draw: D) -> &mut Self {
self.display.draw(draw);
self
}
}
pub struct Map<'a> {
pub screen_coord: Point<i32>,
pub range: Option<(Range<u8>, Range<u8>)>,
pub transparent_color: Cow<'a, [PaletteIdx]>,
pub scale: Option<u8>,
}
impl Map<'_> {
pub fn new(screen_coord: Point<i32>) -> Self {
Self {
screen_coord,
range: None,
transparent_color: Cow::Borrowed(&[]),
scale: None,
}
}
}
impl<'a> Drawable for Map<'a> {
type Return = ();
fn draw(&self, _vram: &mut Display) -> Self::Return {
let (x, y, w, h) = if let Some(r) = &self.range {
(
i32::from(r.0.start),
i32::from(r.1.start),
i32::try_from(r.0.len()).unwrap(),
i32::try_from(r.1.len()).unwrap(),
)
} else {
(-1, -1, -1, -1)
};
let remap = -1;
unsafe {
tic80_sys::sys::map(
x,
y,
w,
h,
self.screen_coord.x,
self.screen_coord.y,
self.transparent_color.as_ptr() as *mut _,
i8::try_from(self.transparent_color.len()).unwrap(),
self.scale.map(|v| v as i8).unwrap_or(-1),
remap,
)
}
}
}
#[bitsize(16)]
pub struct SfxData {
pub volume: u4,
pub wave: u4,
pub apreggio: u4,
pub pitch: u4,
}
#[bitsize(16)]
pub struct SfxMeta {
pub octave: u3,
pub pitch16x: u1,
pub speed: u3,
pub reverse: u1,
pub note: u4,
pub stereo_left: u1,
pub stereo_right: u1,
pub temp: u2,
}
#[bitsize(8)]
pub struct Loop {
start: u4,
size: u4,
}
pub struct Sfx {
pub data: [SfxData; 30],
pub meta: SfxMeta,
pub loops: [Loop; 4],
}
#[bitsize(4)]
#[derive(FromBits)]
pub enum MusicNote {
NoAction,
NoteOff,
Reserved1,
Reserved2,
CFlat,
CSharp,
DFlat,
DSharp,
EFlat,
FFlat,
FSharp,
GFlat,
GSharp,
AFlat,
ASharp,
BFlat,
}
#[bitsize(3)]
#[derive(FromBits)]
pub enum MusicCommand {
Empty,
Volume,
Chord,
Jump,
Slide,
Pitch,
Vibrato,
Delay,
}
#[bitsize(24)]
#[derive(FromBits)]
pub struct PatternRow {
pub note: MusicNote,
pub param1: u4,
pub param2: u4,
pub command: MusicCommand,
pub sfx: u6,
pub octave: u3,
}
pub struct MusicPattern([PatternRow; 64]);
#[bitsize(8)]
#[derive(Copy, Clone, FromBits, DebugBits)]
pub struct TileIdx(pub u8);
pub struct DisplayMap([TileIdx; (tic80_sys::WIDTH * tic80_sys::HEIGHT) as usize]);
impl DisplayMap {
fn offset(p: Point<u32>) -> usize {
usize::try_from(p.y * u32::try_from(tic80_sys::WIDTH).unwrap() + p.x).unwrap()
}
pub fn set(&mut self, p: Point<u32>, id: TileIdx) {
self.0[Self::offset(p)] = id;
}
pub fn get(&mut self, p: Point<u32>) -> TileIdx {
self.0[Self::offset(p)]
}
}
pub struct Display {
pub vram: Vram,
pub sprite_tiles: AddressMem<TilesSpriteBpp>,
pub map: AddressMem<DisplayMap>,
}
impl Display {
pub fn draw<T: Drawable>(&mut self, shape: T) -> <T as Drawable>::Return {
shape.draw(self)
}
pub fn clip(&mut self, clip: Option<(Range<i32>, Range<i32>)>) {
if let Some(clip) = clip {
unsafe {
tic80_sys::sys::clip(
clip.0.start,
clip.1.start,
clip.0.len().try_into().unwrap(),
clip.1.len().try_into().unwrap(),
)
}
} else {
unsafe { tic80_sys::sys::clip(-1, -1, -1, -1) }
}
}
}
pub struct Keyboard {
real_keyboard: AddressMem<[u8; 4]>,
}
impl Keyboard {
pub fn keys(&self) -> impl Iterator<Item = Key> + '_ {
self.real_keyboard
.iter()
.filter(|x| **x != 0)
.map(|x| Key::from(*x))
}
pub fn is_any_pressed(&self) -> bool {
self.keys().next().is_some()
}
pub fn is_any_previously_pressed(&self) -> bool {
unsafe { tic80_sys::sys::keyp(-1, -1, -1) }
}
pub fn keyp(&mut self, key: Key, hold: Option<Ticks>, period: Option<Ticks>) -> bool {
unsafe {
tic80_sys::sys::keyp(
key as i8,
hold.map(|x| i32::try_from(x.0).unwrap()).unwrap_or(-1),
period.map(|x| i32::try_from(x.0).unwrap()).unwrap_or(-1),
)
}
}
}
pub struct Tic80 {
pub display: Display,
pub gamepads: [GamePadEnum; 4],
pub keyboard: Keyboard,
pub mouse: AddressMem<Mouse>,
pub sound: AddressMem<[SoundRegister; 4]>,
pub sfx: AddressMem<[Sfx; 64]>,
pub music_patterns: AddressMem<[MusicPattern; 60]>,
pub sprite_flags: AddressMem<[u8; 512]>,
pub persistent_mem: AddressMem<[u8; 1024]>,
}
#[bitsize(8)]
#[derive(FromBits, Copy, Clone, PartialEq)]
pub struct SectionMask {
pub tiles: bool,
pub sprites: bool,
pub map: bool,
pub sfx: bool,
pub music: bool,
pub palette: bool,
pub flags: bool,
pub screen: bool,
}
impl std::ops::BitOr<Section> for SectionMask {
type Output = SectionMask;
fn bitor(self, rhs: Section) -> Self::Output {
SectionMask::from(self.value | 1 << rhs as u32)
}
}
impl std::ops::BitAnd<Section> for SectionMask {
type Output = SectionMask;
fn bitand(self, rhs: Section) -> Self::Output {
SectionMask::from(self.value & (1 << rhs as u32))
}
}
impl std::ops::BitOr<Section> for Section {
type Output = SectionMask;
fn bitor(self, rhs: Section) -> Self::Output {
SectionMask::from(0) | self | rhs
}
}
#[derive(Copy, Clone, PartialEq)]
pub enum Section {
Tiles,
Sprites,
Map,
Sfx,
Music,
Palette,
Flags,
Screen,
}
impl Tic80 {
pub fn sync(&mut self, mask: Option<SectionMask>, bank: u8, to_cart: bool) {
unsafe {
tic80_sys::sys::sync(
mask.map(u8::from).map(i32::from).unwrap_or(0),
bank,
to_cart,
)
}
}
pub fn exit(&mut self) {
unsafe { tic80_sys::sys::exit() }
}
pub fn tstamp(&self) -> std::time::SystemTime {
std::time::SystemTime::UNIX_EPOCH
+ std::time::Duration::from_secs(unsafe { tic80_sys::sys::tstamp() }.into())
}
pub fn time(&self) -> Duration {
Duration::try_from_secs_f32(unsafe { tic80_sys::sys::time() / 1000.0 }).unwrap()
}
}
pub struct AddressMem<V> {
addr: *mut u8,
_phantom: PhantomData<V>,
}
impl<V> AddressMem<V> {
unsafe fn new(addr: *mut u8) -> Self {
Self {
addr,
_phantom: PhantomData,
}
}
}
impl<V> std::ops::Deref for AddressMem<V> {
type Target = V;
fn deref(&self) -> &Self::Target {
unsafe { self.addr.cast::<V>().as_ref() }.unwrap()
}
}
impl<V> std::ops::DerefMut for AddressMem<V> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.addr.cast::<V>().as_mut() }.unwrap()
}
}
#[derive(Debug, Copy, Clone)]
pub struct MouseSprite(u8);
#[derive(Debug, Copy, Clone)]
pub enum MouseCursor {
Off,
Sprite(MouseSprite),
Arrow,
Hand,
IBeam,
Reserved(u8),
}
impl TryFrom<SpriteIdxBpp4> for MouseSprite {
type Error = ();
fn try_from(f: SpriteIdxBpp4) -> Result<MouseSprite, Self::Error> {
let base = Bpp4::FG_START.value() + 1;
let end = base + 128;
if (base..=end).contains(&f.idx.value()) {
Ok(MouseSprite(
u8::try_from(f.idx.value() - Bpp4::FG_START.value()).unwrap(),
))
} else {
Err(())
}
}
}
impl TryFrom<SpriteIdxBpp2> for MouseSprite {
type Error = ();
fn try_from(f: SpriteIdxBpp2) -> Result<MouseSprite, Self::Error> {
let base = Bpp2::FG_START.value() + 1;
let end = base + 128;
if (base..=end).contains(&f.idx.value()) {
Ok(MouseSprite(
u8::try_from(f.idx.value() - Bpp2::FG_START.value()).unwrap(),
))
} else {
Err(())
}
}
}
impl TryFrom<SpriteIdxBpp1> for MouseSprite {
type Error = ();
fn try_from(f: SpriteIdxBpp1) -> Result<MouseSprite, Self::Error> {
let base = Bpp1::FG_START.value() + 1;
let end = base + 128;
if (base..=end).contains(&f.idx.value()) {
Ok(MouseSprite(
u8::try_from(f.idx.value() - Bpp1::FG_START.value()).unwrap(),
))
} else {
Err(())
}
}
}
impl MouseSprite {
pub fn sprite<T, V>(&self) -> SpriteIdx<T, V>
where
T: Bpp<SpriteIdxSize = V> + std::fmt::Debug,
V: Number + std::ops::Add<V, Output = V>,
{
SpriteIdx {
idx: V::try_new(self.0.into()).unwrap() + T::FG_START,
_phantom: PhantomData,
}
}
pub fn bpp4(&self) -> SpriteIdxBpp4 {
self.sprite()
}
pub fn bpp2(&self) -> SpriteIdxBpp2 {
self.sprite()
}
pub fn bpp1(&self) -> SpriteIdxBpp1 {
self.sprite()
}
}
impl From<u8> for MouseCursor {
fn from(f: u8) -> MouseCursor {
match f {
0 => Self::Off,
v @ 1..=127 => Self::Sprite(MouseSprite(v)),
128 => Self::Arrow,
129 => Self::Hand,
130 => Self::IBeam,
v => MouseCursor::Reserved(v),
}
}
}
impl From<MouseCursor> for u8 {
fn from(f: MouseCursor) -> u8 {
match f {
MouseCursor::Off => 0,
MouseCursor::Sprite(m) => m.0,
MouseCursor::Arrow => 128,
MouseCursor::Hand => 129,
MouseCursor::IBeam => 130,
MouseCursor::Reserved(v) => v,
}
}
}
impl From<MouseCursorByte> for MouseCursor {
fn from(f: MouseCursorByte) -> MouseCursor {
MouseCursor::from(f.value)
}
}
impl From<MouseCursor> for MouseCursorByte {
fn from(f: MouseCursor) -> MouseCursorByte {
MouseCursorByte::new(u8::from(f))
}
}
#[bitsize(8)]
#[derive(Copy, Clone)]
pub struct MouseCursorByte(u8);
pub struct Vram {
pub screen: Screen,
pub palette: AddressMem<[Palette; 16]>,
pub palette_map: AddressMem<[PalettePair; 8]>,
pub border_color: AddressMem<PaletteIdx>,
pub screen_offset: AddressMem<Point<i8>>,
pub mouse_cursor: AddressMem<MouseCursorByte>,
pub blit_segment: AddressMem<BlitRegister>,
}
pub enum Style {
Filled,
Border,
}
struct Circle {
center: Point<i32>,
radius: i32,
color: PaletteIdx,
style: Style,
}
impl Drawable for Circle {
type Return = ();
fn draw(&self, _vram: &mut Display) {
let f = match self.style {
Style::Filled => tic80_sys::sys::circ,
Style::Border => tic80_sys::sys::circb,
};
unsafe {
f(
self.center.x,
self.center.y,
self.radius,
u8::from(self.color.value),
)
}
}
}
struct Ellipse {
center: Point<i32>,
radius_x: u32,
radius_y: u32,
color: PaletteIdx,
style: Style,
}
impl Drawable for Ellipse {
type Return = ();
fn draw(&self, _vram: &mut Display) {
let f = match self.style {
Style::Filled => tic80_sys::sys::elli,
Style::Border => tic80_sys::sys::ellib,
};
unsafe {
f(
self.center.x,
self.center.y,
i32::try_from(self.radius_x).unwrap(),
i32::try_from(self.radius_y).unwrap(),
u8::from(self.color.value),
)
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(C)]
pub struct Point<T> {
pub x: T,
pub y: T,
}
impl<T> From<(T, T)> for Point<T> {
fn from(f: (T, T)) -> Self {
Point { x: f.0, y: f.1 }
}
}
impl<T> From<[T; 2]> for Point<T>
where
T: Copy,
{
fn from(f: [T; 2]) -> Self {
Point { x: f[0], y: f[1] }
}
}
impl From<Point<u8>> for Point<u16> {
fn from(f: Point<u8>) -> Self {
Point {
x: f.x.into(),
y: f.y.into(),
}
}
}
impl From<Point<u8>> for Point<f32> {
fn from(f: Point<u8>) -> Self {
Point {
x: f.x.into(),
y: f.y.into(),
}
}
}
impl From<Point<u8>> for Point<u32> {
fn from(f: Point<u8>) -> Self {
Point {
x: f.x.into(),
y: f.y.into(),
}
}
}
impl From<Point<u8>> for Point<i32> {
fn from(f: Point<u8>) -> Self {
Point {
x: f.x.into(),
y: f.y.into(),
}
}
}
struct ColorPixel {
point: Point<i32>,
color: PaletteIdx,
}
impl Drawable for ColorPixel {
type Return = ();
fn draw(&self, _vram: &mut Display) {
unsafe {
tic80_sys::sys::pix(self.point.x, self.point.y, u8::from(self.color.value) as i8)
};
}
}
#[derive(Copy, Clone)]
#[repr(i32)]
pub enum TextureSource {
Sprite = 0,
Map = 1,
NextVBank = 2,
}
pub struct TexturedTriangle<'a> {
a: Point<f32>,
b: Point<f32>,
c: Point<f32>,
uv_a: Point<f32>,
uv_b: Point<f32>,
uv_c: Point<f32>,
source: TextureSource,
transparent: Cow<'a, [PaletteIdx]>,
z1: f32,
z2: f32,
z3: f32,
depth: bool,
}
impl Drawable for TexturedTriangle<'_> {
type Return = ();
fn draw(&self, _vram: &mut Display) {
unsafe {
tic80_sys::sys::ttri(
self.a.x,
self.a.y,
self.b.x,
self.b.y,
self.c.x,
self.c.y,
self.uv_a.x,
self.uv_a.y,
self.uv_b.x,
self.uv_b.y,
self.uv_c.x,
self.uv_c.y,
self.source as i32,
self.transparent.as_ptr() as *mut _,
u8::try_from(self.transparent.len()).unwrap() as i8,
self.z1,
self.z2,
self.z3,
self.depth,
)
}
}
}
pub struct Sprite<'a, T, V> {
pub idx: SpriteIdx<T, V>,
pub top_left: Point<i32>,
pub transparent: Cow<'a, [PaletteIdx]>,
pub scale: Option<i32>,
pub flip: Option<tic80_sys::Flip>,
pub rotate: Option<tic80_sys::Rotate>,
pub width: Option<i32>,
pub height: Option<i32>,
}
impl<'a, T, V> Sprite<'a, T, V> {
pub fn new(idx: SpriteIdx<T, V>, top_left: Point<i32>) -> Self {
Self {
idx,
top_left,
transparent: (&[][..]).into(),
scale: None,
flip: None,
rotate: None,
width: None,
height: None,
}
}
}
impl<T, V> Drawable for Sprite<'_, T, V>
where
T: Bpp<SpriteIdxSize = V>,
V: Copy + bilge::prelude::Number,
u16: From<<V as bilge::prelude::Number>::UnderlyingType>,
{
type Return = ();
fn draw(&self, display: &mut Display) {
display
.vram
.blit_segment
.set_blit(T::blit_segment().value());
unsafe {
tic80_sys::sys::spr(
self.idx.value().into(),
self.top_left.x,
self.top_left.y,
self.transparent.as_ptr() as *const u8,
u8::try_from(self.transparent.len()).unwrap() as i8,
self.scale.unwrap_or(-1),
self.flip.map(|v| v as i32).unwrap_or(-1),
self.rotate.map(|v| v as i32).unwrap_or(-1),
self.width.unwrap_or(-1),
self.height.unwrap_or(-1),
)
}
}
}
pub struct Triangle {
pub a: Point<f32>,
pub b: Point<f32>,
pub c: Point<f32>,
pub color: PaletteIdx,
pub style: Style,
}
impl Drawable for Triangle {
type Return = ();
fn draw(&self, _vram: &mut Display) {
let f = match self.style {
Style::Filled => tic80_sys::sys::tri,
Style::Border => tic80_sys::sys::trib,
};
unsafe {
f(
self.a.x,
self.a.y,
self.b.x,
self.b.y,
self.c.x,
self.c.y,
u8::from(self.color.value),
)
}
}
}
pub struct Font<'a> {
pub text: Cow<'a, CStr>,
pub pos: Point<i32>,
pub transparent_colors: Cow<'a, [PaletteIdx]>,
pub char_width: Option<u8>,
pub char_height: Option<u8>,
pub fixed: bool,
pub scale: Option<i32>,
pub alt: bool,
}
impl<'a> Font<'a> {
pub fn new(text: Cow<'a, CStr>, pos: Point<i32>) -> Self {
Self {
text,
pos,
transparent_colors: (&[][..]).into(),
char_width: None,
char_height: None,
fixed: false,
scale: None,
alt: false,
}
}
}
impl<'a> Drawable for Font<'a> {
type Return = u32;
fn draw(&self, _vram: &mut Display) -> Self::Return {
let r = unsafe {
tic80_sys::sys::font(
self.text.as_ptr().cast(),
self.pos.x,
self.pos.y,
self.transparent_colors.as_ptr().cast(),
u8::try_from(self.transparent_colors.len()).unwrap() as i8,
self.char_width.map(|v| v as i8).unwrap_or(-1),
self.char_height.map(|v| v as i8).unwrap_or(-1),
self.fixed,
self.scale.unwrap_or(-1),
self.alt,
)
};
u32::try_from(r).unwrap()
}
}
pub struct Text<'a> {
pub text: Cow<'a, CStr>,
pub pos: Option<Point<i32>>,
pub color: Option<PaletteIdx>,
pub fixed: bool,
pub scale: Option<u32>,
pub small_font: bool,
}
impl<'a> Text<'a> {
pub fn new(text: Cow<'a, CStr>) -> Self {
Self {
text,
pos: None,
color: None,
fixed: false,
scale: None,
small_font: false,
}
}
pub fn with_pos(&mut self, pos: Point<i32>) -> &mut Self {
self.pos = Some(pos);
self
}
}
impl<'a> Drawable for Text<'a> {
type Return = u32;
fn draw(&self, _vram: &mut Display) -> Self::Return {
let r = unsafe {
tic80_sys::sys::print(
self.text.as_ptr().cast(),
self.pos.map(|p| p.x).unwrap_or(0),
self.pos.map(|p| p.y).unwrap_or(0),
self.color.map(|c| i32::from(c.value.value())).unwrap_or(15),
self.fixed,
self.scale.map(|s| i32::try_from(s).unwrap()).unwrap_or(1),
self.small_font,
)
};
u32::try_from(r).unwrap()
}
}
pub struct Line {
pub start: Point<f32>,
pub end: Point<f32>,
pub palette: PaletteIdx,
}
pub struct Rect {
pub top_left: Point<i32>,
pub width: u32,
pub height: u32,
pub color: PaletteIdx,
pub style: Style,
}
impl Rect {
pub fn from_points(a: Point<i32>, b: Point<i32>, color: PaletteIdx, style: Style) -> Self {
let top_left = Point {
x: std::cmp::min(a.x, b.x),
y: std::cmp::min(a.y, b.y),
};
let bottom_right = Point {
x: std::cmp::max(a.x, b.x),
y: std::cmp::max(a.y, b.y),
};
Rect {
top_left,
width: u32::try_from(bottom_right.x - top_left.x).unwrap(),
height: u32::try_from(bottom_right.y - top_left.y).unwrap(),
color,
style,
}
}
}
impl Drawable for Rect {
type Return = ();
fn draw(&self, _vram: &mut Display) {
let f = match self.style {
Style::Filled => tic80_sys::sys::rect,
Style::Border => tic80_sys::sys::rectb,
};
unsafe {
f(
self.top_left.x,
self.top_left.y,
i32::try_from(self.width).unwrap(),
i32::try_from(self.height).unwrap(),
u8::from(self.color.value),
)
}
}
}
impl Drawable for Line {
type Return = ();
fn draw(&self, _vram: &mut Display) {
unsafe {
tic80_sys::sys::line(
self.start.x,
self.start.y,
self.end.x,
self.end.y,
u32::from(self.palette.value).try_into().unwrap(),
)
}
}
}
#[derive(Copy, Clone)]
pub enum VramBank {
Zero,
One,
}
impl Vram {
pub fn switch_bank(&mut self, bank: VramBank) {
unsafe {
tic80_sys::sys::vbank(bank as u8);
}
}
pub fn clear(&mut self, idx: Option<PaletteIdx>) {
unsafe { tic80_sys::sys::cls(idx.map(|idx| u8::from(idx.value)).unwrap_or(0)) };
}
}
pub struct PaletteIter<'a, B, I> {
offset_byte: Option<(u8, u8)>,
palettes: I,
_phantom: PhantomData<&'a B>,
}
impl<'a, B, I> Iterator for PaletteIter<'a, B, I>
where
B: Bpp,
u8: std::ops::BitAnd<
<<B as Bpp>::PaletteIdxSize as bilge::prelude::Number>::UnderlyingType,
Output = u8,
>,
I: Iterator<Item = u8>,
{
type Item = PaletteIdx;
fn next(&mut self) -> Option<Self::Item> {
if self.offset_byte.is_none() {
self.offset_byte = Some((0, self.palettes.next()?));
}
let (offset, byte) = self.offset_byte.as_mut()?;
let bits = (*byte >> *offset) & <B as Bpp>::PaletteIdxSize::MAX.value();
*offset += u8::try_from(<B as Bpp>::PaletteIdxSize::BITS).unwrap();
if u32::from(*offset) >= u8::BITS {
self.offset_byte = None;
}
Some(PaletteIdx::new(u4::try_new(bits).unwrap()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (min, max) = self.palettes.size_hint();
let each_bits = <B as Bpp>::PaletteIdxSize::BITS;
let num_palettes = |c| {
c * u8::BITS as usize / each_bits
+ self
.offset_byte
.map(|(o, _b)| usize::try_from(u8::BITS - u32::from(o)).unwrap())
.unwrap_or(0)
/ each_bits
};
(num_palettes(min), max.map(num_palettes))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let total_bits = n * <B as Bpp>::PaletteIdxSize::BITS;
let offset = if let Some((o, _b)) = &mut self.offset_byte {
let remain = usize::try_from(u8::BITS - u32::from(*o)).unwrap();
if total_bits < remain {
*o += u8::try_from(total_bits).unwrap();
return self.next();
} else if total_bits == remain {
self.offset_byte = None;
return self.next();
}
remain
} else {
0
};
let new_byte = self
.palettes
.nth((offset + total_bits) / u8::BITS as usize)?;
let new_offset = (offset + total_bits) % u8::BITS as usize;
self.offset_byte = Some((u8::try_from(new_offset).unwrap(), new_byte));
self.next()
}
}
unsafe impl<A, B> Zeroable for Tile<A, B> {}
unsafe impl<A, B> Pod for Tile<A, B>
where
A: Copy + 'static,
B: Copy + 'static,
{
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Tile<A, B> {
pub tile: A,
_phantom: PhantomData<B>,
}
const PALETTES_PER_TILE: usize = 64;
impl<B, N> std::str::FromStr for Tile<<B as Bpp>::Tile, B>
where
B: Bpp<PaletteIdxSize = N>,
N: bilge::prelude::Number<UnderlyingType = u8>,
{
type Err = PaletteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.chars().count() != PALETTES_PER_TILE {
return Err(PaletteError::TooFewPalettes);
}
let mut tile = Self::default();
let valid_char = |c: char| {
c.to_digit(16)
.map(|p| PaletteIdx::new(u4::new(u8::try_from(p).unwrap())))
.ok_or(PaletteError::InvalidPaletteChar)
};
s.chars().map(valid_char).try_for_each(|v| v.map(|_| ()))?;
let count = tile.from_palettes(s.chars().map(valid_char).map(Result::unwrap))?;
assert_eq!(count, PALETTES_PER_TILE);
Ok(tile)
}
}
impl<A, B> Default for Tile<A, B>
where
A: Default,
{
fn default() -> Self {
Tile {
tile: A::default(),
_phantom: PhantomData,
}
}
}
impl<A, B> Tile<A, B>
where
A: AsRef<[u8]>,
{
pub fn palettes(&self) -> PaletteIter<B, std::iter::Copied<std::slice::Iter<'_, u8>>> {
PaletteIter {
offset_byte: None,
palettes: self.tile.as_ref().iter().copied(),
_phantom: PhantomData,
}
}
}
#[derive(PartialEq, Debug)]
pub enum PaletteError {
TooFewPalettes,
PaletteIdxTooLarge,
InvalidPaletteChar,
}
impl<A, B, N> Tile<A, B>
where
A: AsMut<[u8]>,
B: Bpp<PaletteIdxSize = N>,
N: bilge::prelude::Number<UnderlyingType = u8>,
{
pub fn from_palettes<I>(&mut self, mut iter: I) -> Result<usize, PaletteError>
where
I: Iterator<Item = PaletteIdx>,
{
let tile = self.tile.as_mut();
let mut written = 0;
for v in tile {
let mut offset: usize = 0;
while offset < u8::BITS as usize {
if let Some(new_p) = iter.next() {
let mask: u8 = <B::PaletteIdxSize as Number>::MAX.value();
*v &= !(mask << offset);
let masked = new_p.value.value() & mask;
if new_p.value.value() != masked {
return Err(PaletteError::PaletteIdxTooLarge);
}
*v |= masked << offset;
written += 1;
offset += B::PaletteIdxSize::BITS;
} else {
return Ok(written);
}
}
}
Ok(written)
}
}
const TILES_SPRITE_BYTE_LEN: usize = 8192 * 2;
pub struct TileSection<'a>(&'a [u8]);
pub struct TileSectionMut<'a>(&'a mut [u8]);
impl TilesSpriteBpp {
pub fn split_mut(&mut self) -> (TileSectionMut<'_>, TileSectionMut<'_>) {
let len = self.0.len();
let (tiles, sprites) = self.0.split_at_mut(len / 2);
(TileSectionMut(tiles), TileSectionMut(sprites))
}
pub fn tiles(&self) -> TileSection {
let (tiles, _sprites) = self.0.split_at(self.0.len() / 2);
TileSection(tiles)
}
pub fn sprites(&self) -> TileSection {
let (_tiles, sprites) = self.0.split_at(self.0.len() / 2);
TileSection(sprites)
}
}
macro_rules! bpp {
($(($sprite:ident, $tile:ident, $field:ident)),*) => {
pub struct TilesSpriteBpp([u8; TILES_SPRITE_BYTE_LEN]);
$(
impl std::ops::Index<$sprite> for TilesSpriteBpp {
type Output = $tile;
fn index(&self, index:$sprite) -> &Self::Output {
&AsRef::<[$tile; TILES_SPRITE_BYTE_LEN / std::mem::size_of::<$tile>()]>::as_ref(self)
[usize::try_from(index.idx.value() ).unwrap()]
}
}
impl std::ops::IndexMut<$sprite> for TilesSpriteBpp {
fn index_mut(&mut self, index:$sprite) -> &mut Self::Output {
&mut AsMut::<[$tile; TILES_SPRITE_BYTE_LEN / std::mem::size_of::<$tile>()]>::as_mut(self)
[usize::try_from(index.idx.value() ).unwrap()]
}
}
impl <'a> AsRef<[$tile]> for TileSection<'a> {
fn as_ref(&self) -> &[$tile] {
unsafe { std::mem::transmute(&self.0[..]) }
}
}
impl <'a> AsRef<[$tile]> for TileSectionMut<'a> {
fn as_ref(&self) -> &[$tile] {
unsafe { std::mem::transmute(&self.0[..]) }
}
}
impl <'a> AsMut<[$tile]> for TileSectionMut<'a> {
fn as_mut(&mut self) -> &mut [$tile] {
unsafe { std::mem::transmute(&mut self.0[..]) }
}
}
impl AsRef<[$tile; TILES_SPRITE_BYTE_LEN / std::mem::size_of::<$tile>()]> for TilesSpriteBpp {
fn as_ref(&self) -> &[$tile; TILES_SPRITE_BYTE_LEN / std::mem::size_of::<$tile>()] {
unsafe { std::mem::transmute(&self.0) }
}
}
impl AsRef<[$tile]> for TilesSpriteBpp {
fn as_ref(&self) -> &[$tile] {
unsafe { std::mem::transmute(&self.0[..]) }
}
}
impl AsMut<[$tile; TILES_SPRITE_BYTE_LEN / std::mem::size_of::<$tile>()]> for TilesSpriteBpp {
fn as_mut(&mut self) -> &mut [$tile; TILES_SPRITE_BYTE_LEN / std::mem::size_of::<$tile>()] {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl AsMut<[$tile]> for TilesSpriteBpp {
fn as_mut(&mut self) -> &mut [$tile] {
unsafe { std::mem::transmute(&mut self.0[..]) }
}
}
)*
}
}
bpp! {
(SpriteIdxBpp1, TileBpp1, bpp1),
(SpriteIdxBpp2, TileBpp2, bpp2),
(SpriteIdxBpp4, TileBpp4, bpp4)
}
#[bitsize(8)]
#[derive(FromBits, Copy, Clone)]
pub struct PixelQuad {
pub a: u2,
pub b: u2,
pub c: u2,
pub d: u2,
}
#[bitsize(8)]
#[derive(FromBits, Copy, Clone)]
pub struct PixelOct {
pub a: u1,
pub b: u1,
pub c: u1,
pub d: u1,
pub e: u1,
pub f: u1,
pub g: u1,
pub h: u1,
}
#[bitsize(8)]
#[derive(FromBits, Copy, Clone, DebugBits, PartialEq)]
pub struct PixelPair {
pub low: PaletteIdx,
pub high: PaletteIdx,
}
unsafe impl Zeroable for PixelPair {}
unsafe impl Pod for PixelPair {}
impl<'a> From<&'a mut PixelPair> for VolatilePixelPairMut<'a> {
fn from(p: &mut PixelPair) -> VolatilePixelPairMut {
VolatilePixelPairMut {
pixel: p as *const _ as *mut u8,
_phantom: PhantomData,
}
}
}
impl<'a> From<&'a PixelPair> for VolatilePixelPair<'a> {
fn from(p: &PixelPair) -> VolatilePixelPair {
VolatilePixelPair {
pixel: p as *const _ as *mut u8,
_phantom: PhantomData,
}
}
}
pub struct Screen {
framebuffer: *mut [u8; 16320],
}
#[derive(Debug)]
pub struct VolatilePixelPairMut<'a> {
pixel: *mut u8,
_phantom: PhantomData<&'a ()>,
}
#[derive(Debug)]
pub struct VolatilePixelPair<'a> {
pixel: *mut u8,
_phantom: PhantomData<&'a ()>,
}
impl<'a> VolatilePixelPair<'a> {
pub fn read(&self) -> PixelPair {
PixelPair::from(self.value())
}
pub fn value(&self) -> u8 {
unsafe { std::ptr::read_volatile(self.pixel) }
}
}
impl<'a> VolatilePixelPairMut<'a> {
pub fn write(&mut self, p: PixelPair) {
unsafe { std::ptr::write_volatile(self.pixel, u8::from(p)) };
}
pub fn read(&self) -> PixelPair {
PixelPair::from(self.value())
}
pub fn value(&self) -> u8 {
unsafe { std::ptr::read_volatile(self.pixel) }
}
}
impl Screen {
fn from_offset(offset: usize) -> Self {
Self {
framebuffer: unsafe { tic80_sys::FRAMEBUFFER.byte_add(offset) },
}
}
fn offset(p: Point<u32>) -> usize {
usize::try_from(p.y * u32::try_from(tic80_sys::WIDTH).unwrap() + p.x).unwrap() / 2
}
pub fn pixel(&self, point: Point<u32>) -> Option<PaletteIdx> {
let offset = Self::offset(point);
self.pixels().nth(offset)
}
pub fn set_pixel(&mut self, point: Point<u32>, color: PaletteIdx) -> Result<(), ()> {
let offset = Self::offset(point);
let mut target = self.pixels_mut().nth(offset).ok_or(())?;
let mut old = target.read();
if point.x % 2 == 0 {
old.set_low(color);
} else {
old.set_high(color)
}
target.write(old);
Ok(())
}
pub fn pixels(&self) -> impl Iterator<Item = PaletteIdx> + '_ {
let (zero, rest) = self.as_bytes();
PaletteIter::<Bpp4, _> {
offset_byte: None,
palettes: std::iter::once(zero)
.map(|z| z.value())
.chain(rest.iter().copied()),
_phantom: PhantomData,
}
}
pub fn pixels_mut(&mut self) -> impl Iterator<Item = VolatilePixelPairMut<'_>> {
let (zero, rest) = self.pixels_slice_mut();
std::iter::once(zero).chain(rest.iter_mut().map(|x| x.into()))
}
pub fn as_bytes(&self) -> (VolatilePixelPair, &[u8; 16319]) {
let fb =
unsafe { std::slice::from_raw_parts(self.framebuffer.byte_add(1) as *mut u8, 16319) };
(
VolatilePixelPair {
pixel: self.framebuffer as *mut u8,
_phantom: PhantomData,
},
bytemuck::cast_slice(fb).try_into().unwrap(),
)
}
pub fn pixels_slice(&self) -> (VolatilePixelPair, &[PixelPair; 16319]) {
let (pp, fb) = self.as_bytes();
(pp, bytemuck::cast_slice(&fb[..]).try_into().unwrap())
}
pub fn pixels_slice_mut(&mut self) -> (VolatilePixelPairMut, &mut [PixelPair; 16319]) {
let fb = unsafe {
std::slice::from_raw_parts_mut(self.framebuffer.byte_add(1) as *mut u8, 16319)
};
(
VolatilePixelPairMut {
pixel: self.framebuffer as *mut u8,
_phantom: PhantomData,
},
bytemuck::cast_slice_mut(fb).try_into().unwrap(),
)
}
}
#[derive(Debug)]
pub struct Palette {
pub r: u8,
pub g: u8,
pub b: u8,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[should_panic]
fn run_test() {
unsafe {
tic80_sys::sys::cls(1);
}
}
#[test]
fn test() {
let v = vec![0u8; tic80_sys::SYSTEM_FONT as usize + 2048];
let mut tic80 = unsafe { Tic80::from_offset(v.as_ptr() as usize) };
assert!(tic80
.display
.vram
.screen
.pixels()
.all(|c| { c == PaletteIdx::new(u4::new(0)) }));
tic80.display.vram.screen.pixels_mut().for_each(|mut p| {
p.write(PixelPair::from(0x15));
});
assert!(tic80
.display
.vram
.screen
.pixels()
.all(|c| c == PaletteIdx::new(u4::new(1)) || c == PaletteIdx::new(u4::new(5))));
let tile = tic80.display.sprite_tiles[SpriteIdx::bpp2(5)];
let palettes: Vec<_> = tile.palettes().collect();
assert_eq!(palettes.len(), PALETTES_PER_TILE);
let tile = tic80.display.sprite_tiles[SpriteIdx::bpp4(5)];
let palettes: Vec<_> = tile.palettes().collect();
assert_eq!(palettes.len(), PALETTES_PER_TILE);
let tile = tic80.display.sprite_tiles[SpriteIdx::bpp1(5)];
let palettes: Vec<_> = tile.palettes().collect();
assert_eq!(palettes.len(), PALETTES_PER_TILE);
for (i, p) in palettes.iter().enumerate() {
assert_eq!(tile.palettes().nth(i), Some(*p));
for j in 0..i {
let mut skipped = tile.palettes().skip(j);
assert_eq!(
skipped.size_hint(),
(PALETTES_PER_TILE - j, Some(PALETTES_PER_TILE - j))
);
assert_eq!(skipped.nth(i - j), Some(*p));
}
}
}
use proptest::prelude::*;
fn arb_tile<B>() -> impl Strategy<Value = Tile<B::Tile, B>>
where
B::Tile: Arbitrary,
B: Bpp,
{
(any::<B::Tile>()).prop_map(|a| Tile {
tile: a,
_phantom: PhantomData,
})
}
proptest! {
#[test]
fn test_palette_iter(tile in arb_tile::<Bpp1>()) {
let palettes: Vec<_> = tile.palettes().collect();
assert_eq!(palettes.len(), PALETTES_PER_TILE);
for (i, p) in palettes.iter().enumerate() {
assert_eq!(tile.palettes().nth(i), Some(*p));
for j in 0..i {
let mut skipped = tile.palettes().skip(j);
assert_eq!(skipped.size_hint(), (PALETTES_PER_TILE - j, Some(PALETTES_PER_TILE - j)));
assert_eq!(skipped.nth(i - j), Some(*p));
}
}
let mut new_tile = Tile::default();
assert_eq!(new_tile.from_palettes(palettes.into_iter()), Ok(PALETTES_PER_TILE));
assert_eq!(tile, new_tile);
}
}
}