rust_libretro_example_core/
lib.rs

1use rust_libretro::{
2    contexts::*, core::Core, env_version, input_descriptors, proc::*, retro_core, sys::*, types::*,
3};
4use std::ffi::CString;
5
6const INPUT_DESCRIPTORS: &[retro_input_descriptor] = &input_descriptors!(
7    { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
8    { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
9    { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
10    { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
11    { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "Action" },
12);
13
14#[derive(CoreOptions)]
15#[categories({
16    "advanced_settings",
17    "Advanced",
18    "Options affecting low-level emulation performance and accuracy."
19},{
20    "not_so_advanced_settings",
21    "Not So Advanced",
22    "Options not affecting low-level emulation performance and accuracy."
23})]
24#[options({
25    "foo_option_1",
26    "Advanced > Speed hack coprocessor X",
27    "Speed hack coprocessor X",
28    "Setting 'Advanced > Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy",
29    "Setting 'Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy",
30    "advanced_settings",
31    {
32        { "false" },
33        { "true" },
34        { "unstable", "Turbo (Unstable)" },
35    }
36}, {
37    "foo_option_2",
38    "Simple > Toggle Something",
39    "Toggle Something",
40    "Setting 'Simple > Toggle Something' to 'true' does something.",
41    "Setting 'Toggle Something' to 'true' does something.",
42    "not_so_advanced_settings",
43    {
44        { "false" },
45        { "true" },
46    }
47})]
48struct ExampleCore {
49    option_1: bool,
50    option_2: bool,
51
52    pixels: Vec<u8>,
53    timer: i64,
54    even: bool,
55}
56
57retro_core!(ExampleCore {
58    option_1: false,
59    option_2: true,
60
61    pixels: vec![0; 800 * 600 * 4],
62    timer: 5_000_001,
63    even: true,
64});
65
66impl Core for ExampleCore {
67    fn get_info(&self) -> SystemInfo {
68        SystemInfo {
69            library_name: CString::new("Example Core").unwrap(),
70            library_version: CString::new(env_version!("CARGO_PKG_VERSION").to_string()).unwrap(),
71            valid_extensions: CString::new("").unwrap(),
72
73            need_fullpath: false,
74            block_extract: false,
75        }
76    }
77
78    fn on_set_environment(&mut self, initial: bool, ctx: &mut SetEnvironmentContext) {
79        if !initial {
80            return;
81        }
82
83        ctx.set_support_no_game(true);
84    }
85
86    fn on_init(&mut self, ctx: &mut InitContext) {
87        let gctx: GenericContext = ctx.into();
88        gctx.set_input_descriptors(INPUT_DESCRIPTORS);
89    }
90
91    fn on_get_av_info(&mut self, _ctx: &mut GetAvInfoContext) -> retro_system_av_info {
92        retro_system_av_info {
93            geometry: retro_game_geometry {
94                base_width: 800,
95                base_height: 600,
96                max_width: 800,
97                max_height: 600,
98                aspect_ratio: 0.0,
99            },
100            timing: retro_system_timing {
101                fps: 60.0,
102                sample_rate: 0.0,
103            },
104        }
105    }
106
107    fn on_load_game(
108        &mut self,
109        _info: Option<retro_game_info>,
110        ctx: &mut LoadGameContext,
111    ) -> Result<(), Box<dyn std::error::Error>> {
112        ctx.set_pixel_format(PixelFormat::XRGB8888);
113        ctx.set_performance_level(0);
114        ctx.enable_frame_time_callback((1000000.0f64 / 60.0).round() as retro_usec_t);
115
116        let gctx: GenericContext = ctx.into();
117        gctx.enable_audio_callback();
118
119        Ok(())
120    }
121
122    fn on_options_changed(&mut self, ctx: &mut OptionsChangedContext) {
123        match ctx.get_variable("foo_option_1") {
124            Some("true") => self.option_1 = true,
125            Some("false") => self.option_1 = false,
126            _ => (),
127        }
128
129        match ctx.get_variable("foo_option_2") {
130            Some("true") => self.option_2 = true,
131            Some("false") => self.option_2 = false,
132            _ => (),
133        }
134    }
135
136    #[inline]
137    fn on_run(&mut self, ctx: &mut RunContext, delta_us: Option<i64>) {
138        let gctx: GenericContext = ctx.into();
139
140        self.timer += delta_us.unwrap_or(16_666);
141
142        let input = unsafe { ctx.get_joypad_bitmask(0, 0) };
143
144        if input.contains(JoypadState::START) && input.contains(JoypadState::SELECT) {
145            return gctx.shutdown();
146        }
147
148        if !ctx.can_dupe() || self.timer >= 1_000_000 || input.contains(JoypadState::A) {
149            self.timer = 0;
150            self.even = !self.even;
151
152            let width = 800u32;
153            let height = 600u32;
154
155            let color_a = if self.even { 0xFF } else { 0 };
156            let color_b = !color_a;
157
158            for (i, chunk) in self.pixels.chunks_exact_mut(4).enumerate() {
159                let x = (i % width as usize) as f64 / width as f64;
160                let y = (i / width as usize) as f64 / height as f64;
161
162                let total = (50.0f64 * x).floor() + (37.5f64 * y).floor();
163                let even = total as usize % 2 == 0;
164
165                let color = if even { color_a } else { color_b };
166
167                chunk.fill(color);
168            }
169
170            ctx.draw_frame(self.pixels.as_ref(), width, height, width as usize * 4);
171        } else if ctx.can_dupe() {
172            ctx.dupe_frame();
173        }
174    }
175
176    fn on_write_audio(&mut self, ctx: &mut AudioContext) {
177        ctx.queue_audio_sample(0, 0);
178    }
179}