tic80_sys/
lib.rs

1// Because this isn't in a separate crate, we have to allow unused code to silence the warnings.
2#![allow(dead_code, unused_macros)]
3#![allow(clippy::missing_safety_doc)]
4
5use std::ffi::CString;
6
7pub use sys::MouseInput;
8
9// Constants
10pub const WIDTH: i32 = 240;
11pub const HEIGHT: i32 = 136;
12
13// TIC-80 RAM
14
15pub const FRAMEBUFFER: *mut [u8; 16320] = 0x00000 as *mut [u8; 16320];
16pub const PALETTE: *mut [u8; 48] = 0x03FC0 as *mut [u8; 48];
17pub const PALETTE_MAP: *mut [u8; 8] = 0x03FF0 as *mut [u8; 8];
18pub const BORDER_COLOR: *mut u8 = 0x03FF8 as *mut u8;
19pub const SCREEN_OFFSET_X: *mut u8 = 0x03FF9 as *mut u8;
20pub const SCREEN_OFFSET_Y: *mut u8 = 0x03FFA as *mut u8;
21pub const MOUSE_CURSOR: *mut u8 = 0x03FFB as *mut u8;
22pub const BLIT_SEGMENT: *mut u8 = 0x03FFC as *mut u8;
23pub const TILES: *mut [u8; 8192] = 0x04000 as *mut [u8; 8192];
24pub const SPRITES: *mut [u8; 8192] = 0x06000 as *mut [u8; 8192];
25pub const MAP: *mut [u8; 32640] = 0x08000 as *mut [u8; 32640];
26pub const GAMEPADS: *mut [u8; 4] = 0x0FF80 as *mut [u8; 4];
27pub const MOUSE: *mut [u8; 4] = 0x0FF84 as *mut [u8; 4];
28pub const KEYBOARD: *mut [u8; 4] = 0x0FF88 as *mut [u8; 4];
29pub const SFX_STATE: *mut [u8; 16] = 0x0FF8C as *mut [u8; 16];
30pub const SOUND_REGISTERS: *mut [u8; 72] = 0x0FF9C as *mut [u8; 72];
31pub const WAVEFORMS: *mut [u8; 256] = 0x0FFE4 as *mut [u8; 256];
32pub const SFX: *mut [u8; 4224] = 0x100E4 as *mut [u8; 4224];
33pub const MUSIC_PATTERNS: *mut [u8; 11520] = 0x11164 as *mut [u8; 11520];
34pub const MUSIC_TRACKS: *mut [u8; 408] = 0x13E64 as *mut [u8; 408];
35pub const SOUND_STATE: *mut [u8; 4] = 0x13FFC as *mut [u8; 4];
36pub const STEREO_VOLUME: *mut [u8; 4] = 0x14000 as *mut [u8; 4];
37pub const PERSISTENT_RAM: *mut [u8; 1024] = 0x14004 as *mut [u8; 1024];
38pub const SPRITE_FLAGS: *mut [u8; 512] = 0x14404 as *mut [u8; 512];
39pub const SYSTEM_FONT: *mut [u8; 2048] = 0x14604 as *mut [u8; 2048];
40
41#[cfg(target_family = "wasm")]
42macro_rules! iface {
43    ($(pub fn $ident:ident($($arg:ident : $ty:ty),*$(,)?) $(-> $ret:ty)?);*$(;)?) => {
44        extern "C" {
45            $(
46                pub fn $ident(
47                    $(
48                        $arg: $ty
49                    ),*
50                ) $(-> $ret)*;
51            )*
52        }
53    };
54}
55
56#[cfg(not(target_family = "wasm"))]
57macro_rules! iface {
58    ($(pub fn $ident:ident($($arg:ident : $ty:ty),*$(,)?) $(-> $ret:ty)?);*$(;)?) => {
59        $(
60            /// # Safety:
61            /// This is normally an external function that is unsafe
62            #[allow(unused,clippy::too_many_arguments)]
63            pub unsafe fn $ident(
64                $(
65                    #[allow(unused)]
66                    $arg: $ty
67                ),*
68            ) $(-> $ret)* {
69                panic!("Attempted to call TIC80: {} which is not supported in this target", stringify!($ident));
70            }
71        )*
72    };
73}
74
75// The functions in the sys module follow the signatures as given in wasm.c.
76// The wrapper functions are designed to be similar to the usual TIC-80 api.
77pub mod sys {
78    #[derive(Default)]
79    #[repr(C)]
80    pub struct MouseInput {
81        pub x: i16,
82        pub y: i16,
83        pub scroll_x: i8,
84        pub scroll_y: i8,
85        pub left: bool,
86        pub middle: bool,
87        pub right: bool,
88    }
89
90    iface! {
91        pub fn btn(index: i32) -> i32;
92        pub fn btnp(index: i32, hold: i32, period: i32) -> bool;
93        pub fn clip(x: i32, y: i32, width: i32, height: i32);
94        pub fn cls(color: u8);
95        pub fn circ(x: i32, y: i32, radius: i32, color: u8);
96        pub fn circb(x: i32, y: i32, radius: i32, color: u8);
97        pub fn elli(x: i32, y: i32, a: i32, b: i32, color: u8);
98        pub fn ellib(x: i32, y: i32, a: i32, b: i32, color: u8);
99        pub fn exit();
100        pub fn fget(sprite_index: i32, flag: i8) -> bool;
101        pub fn fset(sprite_index: i32, flag: i8, value: bool);
102        pub fn font(
103            text: *const u8,
104            x: i32,
105            y: i32,
106            trans_colors: *const u8,
107            trans_count: i8,
108            char_width: i8,
109            char_height: i8,
110            fixed: bool,
111            scale: i32,
112            alt: bool,
113        ) -> i32;
114        pub fn key(index: i32) -> bool;
115        pub fn keyp(index: i8, hold: i32, period: i32) -> bool;
116        pub fn line(x0: f32, y0: f32, x1: f32, y1: f32, color: u8);
117        // `remap` is not yet implemented by the TIC-80 WASM runtime, so for now its type is a raw i32.
118        pub fn map(
119            x: i32,
120            y: i32,
121            w: i32,
122            h: i32,
123            sx: i32,
124            sy: i32,
125            trans_colors: *const u8,
126            color_count: i8,
127            scale: i8,
128            remap: i32,
129        );
130        // These clash with rustc builtins, so they are reimplemented in the wrappers.
131        // pub fn memcpy(dest: i32, src: i32, length: i32);
132        // pub fn memset(address: i32, value: i32, length: i32);
133        pub fn mget(x: i32, y: i32) -> i32;
134        pub fn mset(x: i32, y: i32, value: i32);
135        pub fn mouse(mouse: *mut MouseInput);
136        pub fn music(
137            track: i32,
138            frame: i32,
139            row: i32,
140            repeat: bool,
141            sustain: bool,
142            tempo: i32,
143            speed: i32,
144        );
145        pub fn pix(x: i32, y: i32, color: i8) -> u8;
146        pub fn peek(address: i32, bits: u8) -> u8;
147        pub fn peek4(address: i32) -> u8;
148        pub fn peek2(address: i32) -> u8;
149        pub fn peek1(address: i32) -> u8;
150        pub fn pmem(address: i32, value: i64) -> i32;
151        pub fn poke(address: i32, value: u8, bits: u8);
152        pub fn poke4(address: i32, value: u8);
153        pub fn poke2(address: i32, value: u8);
154        pub fn poke1(address: i32, value: u8);
155        pub fn print(
156            text: *const u8,
157            x: i32,
158            y: i32,
159            color: i32,
160            fixed: bool,
161            scale: i32,
162            alt: bool,
163        ) -> i32;
164        pub fn rect(x: i32, y: i32, w: i32, h: i32, color: u8);
165        pub fn rectb(x: i32, y: i32, w: i32, h: i32, color: u8);
166        pub fn sfx(
167            sfx_id: i32,
168            note: i32,
169            octave: i32,
170            duration: i32,
171            channel: i32,
172            volume_left: i32,
173            volume_right: i32,
174            speed: i32,
175        );
176        pub fn spr(
177            id: i32,
178            x: i32,
179            y: i32,
180            trans_colors: *const u8,
181            color_count: i8,
182            scale: i32,
183            flip: i32,
184            rotate: i32,
185            w: i32,
186            h: i32,
187        );
188        pub fn sync(mask: i32, bank: u8, to_cart: bool);
189        pub fn time() -> f32;
190        pub fn tstamp() -> u32;
191        pub fn trace(text: *const u8, color: u8);
192        pub fn tri(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, color: u8);
193        pub fn trib(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, color: u8);
194        pub fn ttri(
195            x1: f32,
196            y1: f32,
197            x2: f32,
198            y2: f32,
199            x3: f32,
200            y3: f32,
201            u1: f32,
202            v1: f32,
203            u2: f32,
204            v2: f32,
205            u3: f32,
206            v3: f32,
207            tex_src: i32,
208            trans_colors: *const u8,
209            color_count: i8,
210            z1: f32,
211            z2: f32,
212            z3: f32,
213            depth: bool,
214        );
215        pub fn vbank(bank: u8) -> u8;
216    }
217}
218
219// Input
220
221pub fn btn(index: i32) -> bool {
222    unsafe { sys::btn(index) != 0 }
223}
224
225pub fn btn_bits() -> u32 {
226    unsafe { sys::btn(-1) as u32 }
227}
228
229pub fn btnp(index: i32, hold: i32, period: i32) -> bool {
230    unsafe { sys::btnp(index, hold, period) }
231}
232
233pub fn key(index: i32) -> bool {
234    unsafe { sys::key(index) }
235}
236
237pub fn keyp(index: i32, hold: i32, period: i32) -> bool {
238    unsafe { sys::keyp(i8::try_from(index).unwrap(), hold, period) }
239}
240
241pub fn mouse() -> MouseInput {
242    let mut input = MouseInput::default();
243    unsafe {
244        sys::mouse(&mut input as *mut _);
245    }
246    input
247}
248
249// Audio
250
251pub struct MusicOptions {
252    pub frame: i32,
253    pub row: i32,
254    pub repeat: bool,
255    pub sustain: bool,
256    pub tempo: i32,
257    pub speed: i32,
258}
259
260impl Default for MusicOptions {
261    fn default() -> Self {
262        Self {
263            frame: -1,
264            row: -1,
265            repeat: true,
266            sustain: false,
267            tempo: -1,
268            speed: -1,
269        }
270    }
271}
272
273pub fn music(track: i32, opts: MusicOptions) {
274    unsafe {
275        sys::music(
276            track,
277            opts.frame,
278            opts.row,
279            opts.repeat,
280            opts.sustain,
281            opts.tempo,
282            opts.speed,
283        )
284    }
285}
286
287pub struct SfxOptions {
288    pub note: i32,
289    pub octave: i32,
290    pub duration: i32,
291    pub channel: i32,
292    pub volume_left: i32,
293    pub volume_right: i32,
294    pub speed: i32,
295}
296
297impl Default for SfxOptions {
298    fn default() -> Self {
299        Self {
300            note: -1,
301            octave: -1,
302            duration: -1,
303            channel: 0,
304            volume_left: 15,
305            volume_right: 15,
306            speed: 0,
307        }
308    }
309}
310
311pub fn sfx(sfx_id: i32, opts: SfxOptions) {
312    unsafe {
313        sys::sfx(
314            sfx_id,
315            opts.note,
316            opts.octave,
317            opts.duration,
318            opts.channel,
319            opts.volume_left,
320            opts.volume_right,
321            opts.speed,
322        )
323    }
324}
325
326// Graphics
327
328pub fn cls(color: u8) {
329    unsafe { sys::cls(color) }
330}
331
332pub fn clip(x: i32, y: i32, width: i32, height: i32) {
333    unsafe { sys::clip(x, y, width, height) }
334}
335
336pub fn circ(x: i32, y: i32, radius: i32, color: u8) {
337    unsafe { sys::circ(x, y, radius, color) }
338}
339
340pub fn circb(x: i32, y: i32, radius: i32, color: u8) {
341    unsafe { sys::circb(x, y, radius, color) }
342}
343
344pub fn elli(x: i32, y: i32, a: i32, b: i32, color: u8) {
345    unsafe { sys::elli(x, y, a, b, color) }
346}
347
348pub fn ellib(x: i32, y: i32, a: i32, b: i32, color: u8) {
349    unsafe { sys::ellib(x, y, a, b, color) }
350}
351
352pub fn line(x0: f32, y0: f32, x1: f32, y1: f32, color: u8) {
353    unsafe { sys::line(x0, y0, x1, y1, color) }
354}
355
356pub fn pix(x: i32, y: i32, color: u8) {
357    unsafe {
358        sys::pix(x, y, color as i8);
359    }
360}
361
362pub fn get_pix(x: i32, y: i32) -> u8 {
363    unsafe { sys::pix(x, y, -1) }
364}
365
366pub fn rect(x: i32, y: i32, w: i32, h: i32, color: u8) {
367    unsafe { sys::rect(x, y, w, h, color) }
368}
369
370pub fn rectb(x: i32, y: i32, w: i32, h: i32, color: u8) {
371    unsafe { sys::rectb(x, y, w, h, color) }
372}
373
374pub fn tri(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, color: u8) {
375    unsafe { sys::tri(x1, y1, x2, y2, x3, y3, color) }
376}
377
378pub fn trib(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, color: u8) {
379    unsafe { sys::trib(x1, y1, x2, y2, x3, y3, color) }
380}
381
382pub enum TextureSource {
383    Tiles,
384    Map,
385    VBank1,
386}
387
388pub struct TTriOptions<'a> {
389    pub texture_src: TextureSource,
390    pub transparent: &'a [u8],
391    pub z1: f32,
392    pub z2: f32,
393    pub z3: f32,
394    pub depth: bool,
395}
396
397impl Default for TTriOptions<'_> {
398    fn default() -> Self {
399        Self {
400            texture_src: TextureSource::Tiles,
401            transparent: &[],
402            z1: 0.0,
403            z2: 0.0,
404            z3: 0.0,
405            depth: false,
406        }
407    }
408}
409
410#[allow(clippy::too_many_arguments)]
411pub fn ttri(
412    x1: f32,
413    y1: f32,
414    x2: f32,
415    y2: f32,
416    x3: f32,
417    y3: f32,
418    u1: f32,
419    v1: f32,
420    u2: f32,
421    v2: f32,
422    u3: f32,
423    v3: f32,
424    opts: TTriOptions,
425) {
426    unsafe {
427        sys::ttri(
428            x1,
429            y1,
430            x2,
431            y2,
432            x3,
433            y3,
434            u1,
435            v1,
436            u2,
437            v2,
438            u3,
439            v3,
440            opts.texture_src as i32,
441            opts.transparent.as_ptr(),
442            opts.transparent.len() as i8,
443            opts.z1,
444            opts.z2,
445            opts.z3,
446            opts.depth,
447        )
448    }
449}
450
451pub struct MapOptions<'a> {
452    pub x: i32,
453    pub y: i32,
454    pub w: i32,
455    pub h: i32,
456    pub sx: i32,
457    pub sy: i32,
458    pub transparent: &'a [u8],
459    pub scale: i8,
460}
461
462impl Default for MapOptions<'_> {
463    fn default() -> Self {
464        Self {
465            x: 0,
466            y: 0,
467            w: 30,
468            h: 17,
469            sx: 0,
470            sy: 0,
471            transparent: &[],
472            scale: 1,
473        }
474    }
475}
476
477pub fn map(opts: MapOptions) {
478    unsafe {
479        sys::map(
480            opts.x,
481            opts.y,
482            opts.w,
483            opts.h,
484            opts.sx,
485            opts.sy,
486            opts.transparent.as_ptr(),
487            opts.transparent.len() as i8,
488            opts.scale,
489            0,
490        )
491    }
492}
493
494pub fn mget(x: i32, y: i32) -> i32 {
495    unsafe { sys::mget(x, y) }
496}
497
498pub fn mset(x: i32, y: i32, value: i32) {
499    unsafe { sys::mset(x, y, value) }
500}
501
502#[derive(Copy, Clone)]
503#[repr(i32)]
504pub enum Flip {
505    None = 0,
506    Horizontal = 1,
507    Vertical = 2,
508    Both = 3,
509}
510
511#[derive(Copy, Clone)]
512#[repr(i32)]
513pub enum Rotate {
514    None = 0,
515    By90 = 1,
516    By180 = 2,
517    By270 = 3,
518}
519
520pub struct SpriteOptions<'a> {
521    pub transparent: &'a [u8],
522    pub scale: i32,
523    pub flip: Flip,
524    pub rotate: Rotate,
525    pub w: i32,
526    pub h: i32,
527}
528
529impl Default for SpriteOptions<'_> {
530    fn default() -> Self {
531        Self {
532            transparent: &[],
533            scale: 1,
534            flip: Flip::None,
535            rotate: Rotate::None,
536            w: 1,
537            h: 1,
538        }
539    }
540}
541
542pub fn spr(id: i32, x: i32, y: i32, opts: SpriteOptions) {
543    unsafe {
544        sys::spr(
545            id,
546            x,
547            y,
548            opts.transparent.as_ptr(),
549            opts.transparent.len() as i8,
550            opts.scale,
551            opts.flip as i32,
552            opts.rotate as i32,
553            opts.w,
554            opts.h,
555        )
556    }
557}
558
559pub fn fget(sprite_index: i32, flag: i8) -> bool {
560    unsafe { sys::fget(sprite_index, flag) }
561}
562
563pub fn fset(sprite_index: i32, flag: i8, value: bool) {
564    unsafe { sys::fset(sprite_index, flag, value) }
565}
566
567// Text Output
568// The *_raw functions require a null terminated string reference.
569// The *_alloc functions can handle any AsRef<str> type, but require the overhead of allocation.
570// The macros will avoid the allocation if passed a string literal by adding the null terminator at compile time.
571
572pub struct PrintOptions {
573    color: i32,
574    fixed: bool,
575    scale: i32,
576    small_font: bool,
577}
578
579impl Default for PrintOptions {
580    fn default() -> Self {
581        Self {
582            color: 15,
583            fixed: false,
584            scale: 1,
585            small_font: false,
586        }
587    }
588}
589
590pub fn print_raw(text: &str, x: i32, y: i32, opts: PrintOptions) -> i32 {
591    unsafe {
592        sys::print(
593            text.as_ptr(),
594            x,
595            y,
596            opts.color,
597            opts.fixed,
598            opts.scale,
599            opts.small_font,
600        )
601    }
602}
603
604pub fn print_alloc(text: impl AsRef<str>, x: i32, y: i32, opts: PrintOptions) -> i32 {
605    let text = CString::new(text.as_ref()).unwrap();
606    unsafe {
607        sys::print(
608            text.as_ptr() as *const u8,
609            x,
610            y,
611            opts.color,
612            opts.fixed,
613            opts.scale,
614            opts.small_font,
615        )
616    }
617}
618
619// "use tic80::*" causes this to shadow std::print, but that isn't useful here anyway.
620#[macro_export]
621macro_rules! print {
622    ($text: literal, $($args: expr), *) => {
623        $crate::tic80::print_raw(concat!($text, "\0"), $($args), *);
624    };
625    ($text: expr, $($args: expr), *) => {
626        $crate::tic80::print_alloc($text, $($args), *);
627    };
628}
629
630pub struct FontOptions<'a> {
631    transparent: &'a [u8],
632    char_width: i8,
633    char_height: i8,
634    fixed: bool,
635    scale: i32,
636    alt_font: bool,
637}
638
639impl Default for FontOptions<'_> {
640    fn default() -> Self {
641        Self {
642            transparent: &[],
643            char_width: 8,
644            char_height: 8,
645            fixed: false,
646            scale: 1,
647            alt_font: false,
648        }
649    }
650}
651
652pub fn font_raw(text: &str, x: i32, y: i32, opts: FontOptions) -> i32 {
653    unsafe {
654        sys::font(
655            text.as_ptr(),
656            x,
657            y,
658            opts.transparent.as_ptr(),
659            opts.transparent.len() as i8,
660            opts.char_width,
661            opts.char_height,
662            opts.fixed,
663            opts.scale,
664            opts.alt_font,
665        )
666    }
667}
668
669pub fn font_alloc(text: impl AsRef<str>, x: i32, y: i32, opts: FontOptions) -> i32 {
670    let text = CString::new(text.as_ref()).unwrap();
671    unsafe {
672        sys::font(
673            text.as_ptr() as *const u8,
674            x,
675            y,
676            opts.transparent.as_ptr(),
677            opts.transparent.len() as i8,
678            opts.char_width,
679            opts.char_height,
680            opts.fixed,
681            opts.scale,
682            opts.alt_font,
683        )
684    }
685}
686
687// Print a string, avoiding allocation if a literal is passed.
688// NOTE: "use tic80::*" causes this to shadow std::print, but that isn't useful here anyway.
689// #[macro_export]
690// macro_rules! font {
691//     ($text: literal, $($args: expr), *) => {
692//         $crate::tic80::font_raw(concat!($text, "\0"), $($args), *);
693//     };
694//     ($text: expr, $($args: expr), *) => {
695//         $crate::tic80::font_alloc($text, $($args), *);
696//     };
697// }
698
699pub fn trace_alloc(text: impl AsRef<str>, color: u8) {
700    let text = CString::new(text.as_ref()).unwrap();
701    unsafe { sys::trace(text.as_ptr() as *const u8, color) }
702}
703
704// #[macro_export]
705// macro_rules! trace {
706//     ($text: literal, $color: expr) => {
707//         unsafe { crate::tic80::sys::trace(concat!($text, "\0").as_ptr(), $color) }
708//     };
709//     ($text: expr, $color: expr) => {
710//         crate::tic80::trace_alloc($text, $color);
711//     };
712// }
713
714// Memory Access
715// These functions (except pmem) are unsafe, because they can be used to access
716// any of the WASM linear memory being used by the program.
717
718pub unsafe fn memcpy(dest: i32, src: i32, length: usize) {
719    core::ptr::copy(src as *const u8, dest as *mut u8, length)
720}
721
722pub unsafe fn memset(address: i32, value: u8, length: usize) {
723    core::ptr::write_bytes(address as *mut u8, value, length)
724}
725
726pub unsafe fn peek(address: i32) -> u8 {
727    sys::peek(address, 8)
728}
729
730pub unsafe fn peek4(address: i32) -> u8 {
731    sys::peek4(address)
732}
733
734pub unsafe fn peek2(address: i32) -> u8 {
735    sys::peek2(address)
736}
737
738pub unsafe fn peek1(address: i32) -> u8 {
739    sys::peek1(address)
740}
741
742pub unsafe fn poke(address: i32, value: u8) {
743    sys::poke(address, value, 8);
744}
745
746pub unsafe fn poke4(address: i32, value: u8) {
747    sys::poke4(address, value);
748}
749
750pub unsafe fn poke2(address: i32, value: u8) {
751    sys::poke2(address, value);
752}
753
754pub unsafe fn poke1(address: i32, value: u8) {
755    sys::poke1(address, value);
756}
757
758pub unsafe fn sync(mask: i32, bank: u8, to_cart: bool) {
759    sys::sync(mask, bank, to_cart);
760}
761
762pub unsafe fn vbank(bank: u8) {
763    sys::vbank(bank);
764}
765
766pub fn pmem_set(address: i32, value: i32) {
767    unsafe {
768        sys::pmem(address, value as i64);
769    }
770}
771
772pub fn pmem_get(address: i32) -> i32 {
773    unsafe { sys::pmem(address, -1) }
774}
775
776// System Functions
777pub fn exit() {
778    unsafe { sys::exit() }
779}
780
781pub fn time() -> f32 {
782    unsafe { sys::time() }
783}
784
785pub fn tstamp() -> u32 {
786    unsafe { sys::tstamp() }
787}