1use std::ffi::{CStr, c_char, c_void};
8use std::ptr;
9
10use enumflags2::{BitFlags, bitflags};
11
12use crate::{
13 CameraInterface, CameraRequest, ControllerInfo, DiskControlInterfaceVersion, Environment,
14 ExtendedGameInfo, FrameTime, InputDescriptor, InputDescriptorStorage, InputDeviceCapabilities,
15 LedInterface, LocationInterface, LogLevel, MemoryMapDescriptor, MicrophoneInterface,
16 MidiInterface, NetplayClientId, PerfInterface, RumbleInterface, SavestateContext,
17 SensorInterface, SerializationQuirks, SoftwareFramebuffer, SoftwareFramebufferRequest,
18 SubsystemInfo, SystemAvInfo, raw,
19};
20
21#[bitflags]
22#[repr(u32)]
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24pub enum AvEnable {
25 Video = raw::RETRO_AV_ENABLE_VIDEO,
26 Audio = raw::RETRO_AV_ENABLE_AUDIO,
27 FastSavestates = raw::RETRO_AV_ENABLE_FAST_SAVESTATES,
28 HardDisableAudio = raw::RETRO_AV_ENABLE_HARD_DISABLE_AUDIO,
29}
30
31pub type AvEnableFlags = BitFlags<AvEnable>;
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
34pub enum VideoRotation {
35 #[default]
36 Clockwise0,
37 CounterClockwise90,
38 CounterClockwise180,
39 CounterClockwise270,
40}
41
42impl VideoRotation {
43 pub const fn as_raw(self) -> u32 {
44 match self {
45 Self::Clockwise0 => 0,
46 Self::CounterClockwise90 => 1,
47 Self::CounterClockwise180 => 2,
48 Self::CounterClockwise270 => 3,
49 }
50 }
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54pub struct PerformanceLevel(u32);
55
56impl PerformanceLevel {
57 pub const fn new(level: u32) -> Self {
58 Self(level)
59 }
60
61 pub const fn get(self) -> u32 {
62 self.0
63 }
64}
65
66#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
67pub enum ThrottleMode {
68 #[default]
69 None,
70 FrameStepping,
71 FastForward,
72 SlowMotion,
73 Rewinding,
74 Vsync,
75 Unblocked,
76 Unknown(u32),
77}
78
79impl ThrottleMode {
80 pub const fn from_raw(mode: u32) -> Self {
81 match mode {
82 raw::RETRO_THROTTLE_NONE => Self::None,
83 raw::RETRO_THROTTLE_FRAME_STEPPING => Self::FrameStepping,
84 raw::RETRO_THROTTLE_FAST_FORWARD => Self::FastForward,
85 raw::RETRO_THROTTLE_SLOW_MOTION => Self::SlowMotion,
86 raw::RETRO_THROTTLE_REWINDING => Self::Rewinding,
87 raw::RETRO_THROTTLE_VSYNC => Self::Vsync,
88 raw::RETRO_THROTTLE_UNBLOCKED => Self::Unblocked,
89 other => Self::Unknown(other),
90 }
91 }
92
93 pub const fn as_raw(self) -> u32 {
94 match self {
95 Self::None => raw::RETRO_THROTTLE_NONE,
96 Self::FrameStepping => raw::RETRO_THROTTLE_FRAME_STEPPING,
97 Self::FastForward => raw::RETRO_THROTTLE_FAST_FORWARD,
98 Self::SlowMotion => raw::RETRO_THROTTLE_SLOW_MOTION,
99 Self::Rewinding => raw::RETRO_THROTTLE_REWINDING,
100 Self::Vsync => raw::RETRO_THROTTLE_VSYNC,
101 Self::Unblocked => raw::RETRO_THROTTLE_UNBLOCKED,
102 Self::Unknown(mode) => mode,
103 }
104 }
105}
106
107#[derive(Clone, Copy, Debug, PartialEq)]
108pub struct RunLoopRateHz(f32);
109
110impl RunLoopRateHz {
111 pub fn new(hz: f32) -> Option<Self> {
112 (hz.is_finite() && hz > 0.0).then_some(Self(hz))
113 }
114
115 pub const fn get(self) -> f32 {
116 self.0
117 }
118}
119
120#[derive(Clone, Copy, Debug, PartialEq)]
121pub struct ThrottleState {
122 pub mode: ThrottleMode,
123 pub rate: Option<RunLoopRateHz>,
124}
125
126impl ThrottleState {
127 fn from_raw(state: raw::retro_throttle_state) -> Self {
128 Self {
129 mode: ThrottleMode::from_raw(state.mode),
130 rate: RunLoopRateHz::new(state.rate),
131 }
132 }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq)]
136pub struct FastForwardRatio(f32);
137
138impl FastForwardRatio {
139 pub const fn frontend_default() -> Self {
140 Self(-1.0)
141 }
142
143 pub const fn normal_speed() -> Self {
144 Self(1.0)
145 }
146
147 pub const fn unlimited() -> Self {
148 Self(0.0)
149 }
150
151 pub fn multiplier(ratio: f32) -> Option<Self> {
152 (ratio.is_finite() && ratio > 1.0).then_some(Self(ratio))
153 }
154
155 pub const fn get(self) -> f32 {
156 self.0
157 }
158}
159
160#[derive(Clone, Copy, Debug, PartialEq)]
161pub struct FastForwardingOverride {
162 pub ratio: FastForwardRatio,
163 pub fastforward: bool,
164 pub notification: bool,
165 pub inhibit_toggle: bool,
166}
167
168impl FastForwardingOverride {
169 pub fn enable() -> Self {
170 Self {
171 ratio: FastForwardRatio::frontend_default(),
172 fastforward: true,
173 notification: true,
174 inhibit_toggle: false,
175 }
176 }
177
178 pub fn disable() -> Self {
179 Self {
180 fastforward: false,
181 ..Self::enable()
182 }
183 }
184
185 pub fn with_ratio(mut self, ratio: FastForwardRatio) -> Self {
186 self.ratio = ratio;
187 self
188 }
189
190 pub fn with_notification(mut self, notification: bool) -> Self {
191 self.notification = notification;
192 self
193 }
194
195 pub fn with_inhibit_toggle(mut self, inhibit_toggle: bool) -> Self {
196 self.inhibit_toggle = inhibit_toggle;
197 self
198 }
199
200 fn into_raw(self) -> raw::retro_fastforwarding_override {
201 raw::retro_fastforwarding_override {
202 ratio: self.ratio.get(),
203 fastforward: self.fastforward,
204 notification: self.notification,
205 inhibit_toggle: self.inhibit_toggle,
206 }
207 }
208}
209
210#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
211pub enum PowerState {
212 #[default]
213 Unknown,
214 Discharging,
215 Charging,
216 Charged,
217 PluggedIn,
218}
219
220impl PowerState {
221 const fn from_raw(state: raw::retro_power_state) -> Self {
222 match state {
223 raw::retro_power_state::Unknown => Self::Unknown,
224 raw::retro_power_state::Discharging => Self::Discharging,
225 raw::retro_power_state::Charging => Self::Charging,
226 raw::retro_power_state::Charged => Self::Charged,
227 raw::retro_power_state::PluggedIn => Self::PluggedIn,
228 }
229 }
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
233pub struct DevicePower {
234 pub state: PowerState,
235 pub seconds_remaining: Option<u32>,
236 pub percent: Option<u8>,
237}
238
239impl DevicePower {
240 fn from_raw(power: raw::retro_device_power) -> Self {
241 Self {
242 state: PowerState::from_raw(power.state),
243 seconds_remaining: u32::try_from(power.seconds).ok(),
244 percent: u8::try_from(power.percent)
245 .ok()
246 .filter(|percent| *percent <= 100),
247 }
248 }
249}
250
251#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
252pub enum MessageTarget {
253 All,
254 #[default]
255 Osd,
256 Log,
257}
258
259impl MessageTarget {
260 const fn as_raw(self) -> raw::retro_message_target {
261 match self {
262 Self::All => raw::retro_message_target::All,
263 Self::Osd => raw::retro_message_target::Osd,
264 Self::Log => raw::retro_message_target::Log,
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
270pub enum MessageKind {
271 #[default]
272 Notification,
273 NotificationAlt,
274 Status,
275 Progress,
276}
277
278impl MessageKind {
279 const fn as_raw(self) -> raw::retro_message_type {
280 match self {
281 Self::Notification => raw::retro_message_type::Notification,
282 Self::NotificationAlt => raw::retro_message_type::NotificationAlt,
283 Self::Status => raw::retro_message_type::Status,
284 Self::Progress => raw::retro_message_type::Progress,
285 }
286 }
287}
288
289#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
290pub enum MessageProgress {
291 Indeterminate,
292 Percent(u8),
293}
294
295impl MessageProgress {
296 pub fn percent(percent: u8) -> Option<Self> {
297 (percent <= 100).then_some(Self::Percent(percent))
298 }
299
300 const fn as_raw(self) -> i8 {
301 match self {
302 Self::Indeterminate => -1,
303 Self::Percent(percent) => percent as i8,
304 }
305 }
306}
307
308#[derive(Clone, Debug, PartialEq, Eq)]
309pub struct ExtendedMessage {
310 message: String,
311 duration_millis: u32,
312 priority: u32,
313 level: LogLevel,
314 target: MessageTarget,
315 kind: MessageKind,
316 progress: MessageProgress,
317}
318
319impl ExtendedMessage {
320 pub fn new(message: impl Into<String>) -> Self {
321 Self {
322 message: message.into(),
323 duration_millis: 0,
324 priority: 0,
325 level: LogLevel::Info,
326 target: MessageTarget::Osd,
327 kind: MessageKind::Notification,
328 progress: MessageProgress::Indeterminate,
329 }
330 }
331
332 pub fn with_duration_millis(mut self, duration_millis: u32) -> Self {
333 self.duration_millis = duration_millis;
334 self
335 }
336
337 pub fn with_priority(mut self, priority: u32) -> Self {
338 self.priority = priority;
339 self
340 }
341
342 pub fn with_level(mut self, level: LogLevel) -> Self {
343 self.level = level;
344 self
345 }
346
347 pub fn with_target(mut self, target: MessageTarget) -> Self {
348 self.target = target;
349 self
350 }
351
352 pub fn with_kind(mut self, kind: MessageKind) -> Self {
353 self.kind = kind;
354 self
355 }
356
357 pub fn with_progress(mut self, progress: MessageProgress) -> Self {
358 self.progress = progress;
359 self.kind = MessageKind::Progress;
360 self
361 }
362}
363
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
365pub struct AudioLatencyMillis(u32);
366
367impl AudioLatencyMillis {
368 pub const fn new(milliseconds: u32) -> Self {
369 Self(milliseconds)
370 }
371
372 pub const fn as_millis(self) -> u32 {
373 self.0
374 }
375}
376
377#[derive(Clone, Copy, Debug, PartialEq)]
378pub struct RefreshRateHz(f32);
379
380impl RefreshRateHz {
381 pub const fn new(hz: f32) -> Self {
382 Self(hz)
383 }
384
385 pub const fn get(self) -> f32 {
386 self.0
387 }
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
391pub struct AudioSampleRateHz(u32);
392
393impl AudioSampleRateHz {
394 pub const fn new(hz: u32) -> Self {
395 Self(hz)
396 }
397
398 pub const fn get(self) -> u32 {
399 self.0
400 }
401}
402
403#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
404pub enum Language {
405 #[default]
406 English,
407 Japanese,
408 French,
409 Spanish,
410 German,
411 Italian,
412 Dutch,
413 PortugueseBrazil,
414 PortuguesePortugal,
415 Russian,
416 Korean,
417 ChineseTraditional,
418 ChineseSimplified,
419 Esperanto,
420 Polish,
421 Vietnamese,
422 Arabic,
423 Greek,
424 Turkish,
425 Slovak,
426 Persian,
427 Hebrew,
428 Asturian,
429 Finnish,
430 Indonesian,
431 Swedish,
432 Ukrainian,
433 Czech,
434 CatalanValencia,
435 Catalan,
436 BritishEnglish,
437 Hungarian,
438 Belarusian,
439 Galician,
440 Norwegian,
441 Irish,
442 Unknown(i32),
443}
444
445impl Language {
446 pub const fn from_raw(language: i32) -> Self {
447 match language {
448 0 => Self::English,
449 1 => Self::Japanese,
450 2 => Self::French,
451 3 => Self::Spanish,
452 4 => Self::German,
453 5 => Self::Italian,
454 6 => Self::Dutch,
455 7 => Self::PortugueseBrazil,
456 8 => Self::PortuguesePortugal,
457 9 => Self::Russian,
458 10 => Self::Korean,
459 11 => Self::ChineseTraditional,
460 12 => Self::ChineseSimplified,
461 13 => Self::Esperanto,
462 14 => Self::Polish,
463 15 => Self::Vietnamese,
464 16 => Self::Arabic,
465 17 => Self::Greek,
466 18 => Self::Turkish,
467 19 => Self::Slovak,
468 20 => Self::Persian,
469 21 => Self::Hebrew,
470 22 => Self::Asturian,
471 23 => Self::Finnish,
472 24 => Self::Indonesian,
473 25 => Self::Swedish,
474 26 => Self::Ukrainian,
475 27 => Self::Czech,
476 28 => Self::CatalanValencia,
477 29 => Self::Catalan,
478 30 => Self::BritishEnglish,
479 31 => Self::Hungarian,
480 32 => Self::Belarusian,
481 33 => Self::Galician,
482 34 => Self::Norwegian,
483 35 => Self::Irish,
484 other => Self::Unknown(other),
485 }
486 }
487
488 pub const fn as_raw(self) -> i32 {
489 match self {
490 Self::English => 0,
491 Self::Japanese => 1,
492 Self::French => 2,
493 Self::Spanish => 3,
494 Self::German => 4,
495 Self::Italian => 5,
496 Self::Dutch => 6,
497 Self::PortugueseBrazil => 7,
498 Self::PortuguesePortugal => 8,
499 Self::Russian => 9,
500 Self::Korean => 10,
501 Self::ChineseTraditional => 11,
502 Self::ChineseSimplified => 12,
503 Self::Esperanto => 13,
504 Self::Polish => 14,
505 Self::Vietnamese => 15,
506 Self::Arabic => 16,
507 Self::Greek => 17,
508 Self::Turkish => 18,
509 Self::Slovak => 19,
510 Self::Persian => 20,
511 Self::Hebrew => 21,
512 Self::Asturian => 22,
513 Self::Finnish => 23,
514 Self::Indonesian => 24,
515 Self::Swedish => 25,
516 Self::Ukrainian => 26,
517 Self::Czech => 27,
518 Self::CatalanValencia => 28,
519 Self::Catalan => 29,
520 Self::BritishEnglish => 30,
521 Self::Hungarian => 31,
522 Self::Belarusian => 32,
523 Self::Galician => 33,
524 Self::Norwegian => 34,
525 Self::Irish => 35,
526 Self::Unknown(language) => language,
527 }
528 }
529}
530
531impl Environment<'_> {
532 pub fn set_rotation(&mut self, rotation: VideoRotation) -> bool {
533 let mut raw_rotation = rotation.as_raw();
534 self.call_env(
535 raw::RETRO_ENVIRONMENT_SET_ROTATION,
536 (&mut raw_rotation as *mut u32).cast::<c_void>(),
537 )
538 }
539
540 pub fn overscan(&mut self) -> Option<bool> {
541 let mut overscan = false;
542 if self.call_env(
543 raw::RETRO_ENVIRONMENT_GET_OVERSCAN,
544 (&mut overscan as *mut bool).cast::<c_void>(),
545 ) {
546 Some(overscan)
547 } else {
548 None
549 }
550 }
551
552 pub fn can_dupe_frames(&mut self) -> Option<bool> {
553 let mut can_dupe = false;
554 if self.call_env(
555 raw::RETRO_ENVIRONMENT_GET_CAN_DUPE,
556 (&mut can_dupe as *mut bool).cast::<c_void>(),
557 ) {
558 Some(can_dupe)
559 } else {
560 None
561 }
562 }
563
564 pub fn shutdown(&mut self) -> bool {
565 self.call_env(raw::RETRO_ENVIRONMENT_SHUTDOWN, ptr::null_mut())
566 }
567
568 pub fn set_system_av_info(&mut self, info: SystemAvInfo) -> bool {
569 let mut info = info.as_raw();
570 self.call_env(
571 raw::RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO,
572 (&mut info as *mut raw::retro_system_av_info).cast::<c_void>(),
573 )
574 }
575
576 pub fn extended_game_info(&mut self) -> Option<ExtendedGameInfo<'_>> {
577 self.extended_game_infos(1)
578 .and_then(|mut infos| infos.pop())
579 }
580
581 pub fn extended_game_infos(&mut self, count: usize) -> Option<Vec<ExtendedGameInfo<'_>>> {
582 if count == 0 {
583 return Some(Vec::new());
584 }
585
586 let mut infos = ptr::null::<raw::retro_game_info_ext>();
587 if !self.call_env(
588 raw::RETRO_ENVIRONMENT_GET_GAME_INFO_EXT,
589 (&mut infos as *mut *const raw::retro_game_info_ext).cast::<c_void>(),
590 ) || infos.is_null()
591 {
592 return None;
593 }
594
595 let infos = unsafe { std::slice::from_raw_parts(infos, count) };
598 Some(
599 infos
600 .iter()
601 .map(|info| unsafe { ExtendedGameInfo::from_raw(info) })
602 .collect(),
603 )
604 }
605
606 pub fn set_performance_level(&mut self, level: PerformanceLevel) -> bool {
607 let mut raw_level = level.get();
608 self.call_env(
609 raw::RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL,
610 (&mut raw_level as *mut u32).cast::<c_void>(),
611 )
612 }
613
614 pub fn perf_interface(&mut self) -> Option<PerfInterface> {
615 let mut callback = raw::retro_perf_callback::default();
616 if self.call_env(
617 raw::RETRO_ENVIRONMENT_GET_PERF_INTERFACE,
618 (&mut callback as *mut raw::retro_perf_callback).cast::<c_void>(),
619 ) {
620 Some(PerfInterface::from_raw(callback))
621 } else {
622 None
623 }
624 }
625
626 pub fn system_directory(&mut self) -> Option<String> {
627 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY)
628 }
629
630 pub fn libretro_path(&mut self) -> Option<String> {
631 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_LIBRETRO_PATH)
632 }
633
634 pub fn core_assets_directory(&mut self) -> Option<String> {
635 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY)
636 }
637
638 pub fn content_directory(&mut self) -> Option<String> {
639 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY)
640 }
641
642 pub fn save_directory(&mut self) -> Option<String> {
643 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY)
644 }
645
646 pub fn username(&mut self) -> Option<String> {
647 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_USERNAME)
648 }
649
650 pub fn playlist_directory(&mut self) -> Option<String> {
651 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY)
652 }
653
654 pub fn file_browser_start_directory(&mut self) -> Option<String> {
655 self.frontend_string(raw::RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY)
656 }
657
658 pub fn language(&mut self) -> Option<Language> {
659 let mut language = raw::retro_language::English as i32;
660 if self.call_env(
661 raw::RETRO_ENVIRONMENT_GET_LANGUAGE,
662 (&mut language as *mut i32).cast::<c_void>(),
663 ) {
664 Some(Language::from_raw(language))
665 } else {
666 None
667 }
668 }
669
670 pub fn jit_capable(&mut self) -> Option<bool> {
671 let mut capable = false;
672 if self.call_env(
673 raw::RETRO_ENVIRONMENT_GET_JIT_CAPABLE,
674 (&mut capable as *mut bool).cast::<c_void>(),
675 ) {
676 Some(capable)
677 } else {
678 None
679 }
680 }
681
682 pub fn set_support_achievements(&mut self, supported: bool) -> bool {
683 let mut raw_supported = supported;
684 self.call_env(
685 raw::RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS,
686 (&mut raw_supported as *mut bool).cast::<c_void>(),
687 )
688 }
689
690 pub fn audio_video_enable(&mut self) -> Option<AvEnableFlags> {
691 let mut flags = 0u32;
692 if self.call_env(
693 raw::RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE,
694 (&mut flags as *mut u32).cast::<c_void>(),
695 ) {
696 Some(AvEnableFlags::from_bits_truncate(flags))
697 } else {
698 None
699 }
700 }
701
702 pub fn fastforwarding(&mut self) -> Option<bool> {
703 let mut fastforwarding = false;
704 if self.call_env(
705 raw::RETRO_ENVIRONMENT_GET_FASTFORWARDING,
706 (&mut fastforwarding as *mut bool).cast::<c_void>(),
707 ) {
708 Some(fastforwarding)
709 } else {
710 None
711 }
712 }
713
714 pub fn fastforwarding_override_supported(&mut self) -> bool {
715 self.call_env(
716 raw::RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE,
717 ptr::null_mut(),
718 )
719 }
720
721 pub fn set_fastforwarding_override(&mut self, override_: FastForwardingOverride) -> bool {
722 let mut override_ = override_.into_raw();
723 self.call_env(
724 raw::RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE,
725 (&mut override_ as *mut raw::retro_fastforwarding_override).cast::<c_void>(),
726 )
727 }
728
729 pub fn target_refresh_rate(&mut self) -> Option<RefreshRateHz> {
730 let mut hz = 0.0f32;
731 if self.call_env(
732 raw::RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE,
733 (&mut hz as *mut f32).cast::<c_void>(),
734 ) {
735 Some(RefreshRateHz::new(hz))
736 } else {
737 None
738 }
739 }
740
741 pub fn target_sample_rate(&mut self) -> Option<AudioSampleRateHz> {
742 let mut hz = 0u32;
743 if self.call_env(
744 raw::RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE,
745 (&mut hz as *mut u32).cast::<c_void>(),
746 ) {
747 Some(AudioSampleRateHz::new(hz))
748 } else {
749 None
750 }
751 }
752
753 pub fn throttle_state(&mut self) -> Option<ThrottleState> {
754 let mut state = raw::retro_throttle_state::default();
755 if self.call_env(
756 raw::RETRO_ENVIRONMENT_GET_THROTTLE_STATE,
757 (&mut state as *mut raw::retro_throttle_state).cast::<c_void>(),
758 ) {
759 Some(ThrottleState::from_raw(state))
760 } else {
761 None
762 }
763 }
764
765 pub fn savestate_context(&mut self) -> Option<SavestateContext> {
766 let mut context = SavestateContext::Normal.as_raw();
767 if self.call_env(
768 raw::RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT,
769 (&mut context as *mut i32).cast::<c_void>(),
770 ) {
771 Some(SavestateContext::from_raw(context))
772 } else {
773 None
774 }
775 }
776
777 pub fn set_memory_maps(&mut self, descriptors: &[MemoryMapDescriptor<'_>]) -> bool {
778 let Ok(num_descriptors) = u32::try_from(descriptors.len()) else {
779 return false;
780 };
781
782 let mut addrspaces = Vec::with_capacity(descriptors.len());
783 let mut raw_descriptors = Vec::with_capacity(descriptors.len());
784 for descriptor in descriptors {
785 let addrspace = descriptor.addrspace.as_ref().map(crate::sanitize_cstring);
786 let addrspace_ptr = addrspace
787 .as_ref()
788 .map(|addrspace| addrspace.as_ptr())
789 .unwrap_or(ptr::null());
790 raw_descriptors.push(raw::retro_memory_descriptor {
791 flags: descriptor.raw_flags(),
792 ptr: descriptor
793 .ptr
794 .map(|ptr| ptr.as_ptr().cast::<c_void>())
795 .unwrap_or(ptr::null_mut()),
796 offset: descriptor.offset.as_usize(),
797 start: descriptor.start.as_usize(),
798 select: descriptor.select.as_usize(),
799 disconnect: descriptor.disconnect.as_usize(),
800 len: descriptor.len.as_usize(),
801 addrspace: addrspace_ptr,
802 });
803 addrspaces.push(addrspace);
804 }
805
806 let mut map = raw::retro_memory_map {
807 descriptors: raw_descriptors.as_ptr(),
808 num_descriptors,
809 };
810 self.call_env(
811 raw::RETRO_ENVIRONMENT_SET_MEMORY_MAPS,
812 (&mut map as *mut raw::retro_memory_map).cast::<c_void>(),
813 )
814 }
815
816 pub fn set_controller_info(&mut self, ports: &[ControllerInfo]) -> bool {
817 let mut descriptions = Vec::with_capacity(ports.len());
818 let mut raw_description_groups = Vec::with_capacity(ports.len());
819 for port in ports {
820 if port.types.is_empty() {
821 return false;
822 }
823 let Ok(num_types) = u32::try_from(port.types.len()) else {
824 return false;
825 };
826
827 let mut port_descriptions = Vec::with_capacity(port.types.len());
828 let mut raw_descriptions = Vec::with_capacity(port.types.len());
829 for controller in &port.types {
830 let description = crate::sanitize_cstring(&controller.description);
831 raw_descriptions.push(raw::retro_controller_description {
832 desc: description.as_ptr(),
833 id: controller.device.as_raw(),
834 });
835 port_descriptions.push(description);
836 }
837
838 descriptions.push(port_descriptions);
839 raw_description_groups.push((raw_descriptions, num_types));
840 }
841
842 let mut raw_ports = raw_description_groups
843 .iter()
844 .map(|(raw_descriptions, num_types)| raw::retro_controller_info {
845 types: raw_descriptions.as_ptr(),
846 num_types: *num_types,
847 })
848 .collect::<Vec<_>>();
849 raw_ports.push(raw::retro_controller_info::default());
850
851 let ok = self.call_env(
852 raw::RETRO_ENVIRONMENT_SET_CONTROLLER_INFO,
853 raw_ports.as_mut_ptr().cast::<c_void>(),
854 );
855 drop(descriptions);
856 ok
857 }
858
859 pub fn set_proc_address_callback(&mut self) -> bool {
860 let mut interface = raw::retro_get_proc_address_interface {
861 get_proc_address: Some(crate::proc_address_trampoline),
862 };
863 self.call_env(
864 raw::RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK,
865 (&mut interface as *mut raw::retro_get_proc_address_interface).cast::<c_void>(),
866 )
867 }
868
869 pub fn set_subsystem_info(&mut self, subsystems: &[SubsystemInfo]) -> bool {
870 let mut storage = crate::subsystem::SubsystemInfoStorage::new(subsystems);
871 let ok = self.call_env(
872 raw::RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO,
873 storage.raw.as_mut_ptr().cast::<c_void>(),
874 );
875 if ok {
876 self.state.subsystem_info = Some(storage);
877 }
878 ok
879 }
880
881 pub fn disk_control_interface_version(&mut self) -> Option<DiskControlInterfaceVersion> {
882 let mut version = 0u32;
883 if self.call_env(
884 raw::RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION,
885 (&mut version as *mut u32).cast::<c_void>(),
886 ) {
887 Some(DiskControlInterfaceVersion::new(version))
888 } else {
889 None
890 }
891 }
892
893 pub fn set_disk_control_interface(&mut self) -> bool {
894 let mut callback = raw::retro_disk_control_callback {
895 set_eject_state: Some(crate::disk_set_eject_state_trampoline),
896 get_eject_state: Some(crate::disk_get_eject_state_trampoline),
897 get_image_index: Some(crate::disk_get_image_index_trampoline),
898 set_image_index: Some(crate::disk_set_image_index_trampoline),
899 get_num_images: Some(crate::disk_get_num_images_trampoline),
900 replace_image_index: Some(crate::disk_replace_image_index_trampoline),
901 add_image_index: Some(crate::disk_add_image_index_trampoline),
902 };
903 self.call_env(
904 raw::RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE,
905 (&mut callback as *mut raw::retro_disk_control_callback).cast::<c_void>(),
906 )
907 }
908
909 pub fn clear_disk_control_interface(&mut self) -> bool {
910 self.call_env(
911 raw::RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE,
912 ptr::null_mut(),
913 )
914 }
915
916 pub fn set_disk_control_ext_interface(&mut self) -> bool {
917 let mut callback = raw::retro_disk_control_ext_callback {
918 set_eject_state: Some(crate::disk_set_eject_state_trampoline),
919 get_eject_state: Some(crate::disk_get_eject_state_trampoline),
920 get_image_index: Some(crate::disk_get_image_index_trampoline),
921 set_image_index: Some(crate::disk_set_image_index_trampoline),
922 get_num_images: Some(crate::disk_get_num_images_trampoline),
923 replace_image_index: Some(crate::disk_replace_image_index_trampoline),
924 add_image_index: Some(crate::disk_add_image_index_trampoline),
925 set_initial_image: Some(crate::disk_set_initial_image_trampoline),
926 get_image_path: Some(crate::disk_get_image_path_trampoline),
927 get_image_label: Some(crate::disk_get_image_label_trampoline),
928 };
929 self.call_env(
930 raw::RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE,
931 (&mut callback as *mut raw::retro_disk_control_ext_callback).cast::<c_void>(),
932 )
933 }
934
935 pub fn clear_disk_control_ext_interface(&mut self) -> bool {
936 self.call_env(
937 raw::RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE,
938 ptr::null_mut(),
939 )
940 }
941
942 pub fn set_netpacket_interface(&mut self, protocol_version: Option<&str>) -> bool {
943 let mut storage = crate::netplay::NetpacketInterfaceStorage::new(protocol_version);
944 let ok = self.call_env(
945 raw::RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE,
946 (&mut storage.raw as *mut raw::retro_netpacket_callback).cast::<c_void>(),
947 );
948 if ok {
949 self.state.netpacket_interface = Some(storage);
950 }
951 ok
952 }
953
954 pub fn clear_netpacket_interface(&mut self) -> bool {
955 let ok = self.call_env(
956 raw::RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE,
957 ptr::null_mut(),
958 );
959 if ok {
960 self.state.netpacket_interface = None;
961 }
962 ok
963 }
964
965 pub fn current_software_framebuffer(
966 &mut self,
967 request: SoftwareFramebufferRequest,
968 ) -> Option<SoftwareFramebuffer> {
969 let mut framebuffer = request.into_raw();
970 if self.call_env(
971 raw::RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER,
972 (&mut framebuffer as *mut raw::retro_framebuffer).cast::<c_void>(),
973 ) {
974 SoftwareFramebuffer::from_raw(framebuffer)
975 } else {
976 None
977 }
978 }
979
980 pub fn set_minimum_audio_latency(&mut self, latency: Option<AudioLatencyMillis>) -> bool {
981 match latency {
982 Some(latency) => {
983 let mut milliseconds = latency.as_millis();
984 self.call_env(
985 raw::RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY,
986 (&mut milliseconds as *mut u32).cast::<c_void>(),
987 )
988 }
989 None => self.call_env(
990 raw::RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY,
991 ptr::null_mut(),
992 ),
993 }
994 }
995
996 pub fn set_audio_buffer_status_callback(&mut self, enabled: bool) -> bool {
997 if enabled {
998 let mut callback = raw::retro_audio_buffer_status_callback {
999 callback: Some(crate::audio_buffer_status_trampoline),
1000 };
1001 self.call_env(
1002 raw::RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK,
1003 (&mut callback as *mut raw::retro_audio_buffer_status_callback).cast::<c_void>(),
1004 )
1005 } else {
1006 self.call_env(
1007 raw::RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK,
1008 ptr::null_mut(),
1009 )
1010 }
1011 }
1012
1013 pub fn audio_callback_available(&mut self) -> bool {
1014 self.call_env(raw::RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK, ptr::null_mut())
1015 }
1016
1017 pub fn set_audio_callback(&mut self) -> bool {
1018 let mut callback = raw::retro_audio_callback {
1019 callback: Some(crate::audio_callback_trampoline),
1020 set_state: Some(crate::audio_set_state_trampoline),
1021 };
1022 self.call_env(
1023 raw::RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK,
1024 (&mut callback as *mut raw::retro_audio_callback).cast::<c_void>(),
1025 )
1026 }
1027
1028 pub fn clear_audio_callback(&mut self) -> bool {
1029 let mut callback = raw::retro_audio_callback::default();
1030 self.call_env(
1031 raw::RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK,
1032 (&mut callback as *mut raw::retro_audio_callback).cast::<c_void>(),
1033 )
1034 }
1035
1036 pub fn set_frame_time_callback(&mut self, reference: FrameTime) -> bool {
1037 let mut callback = raw::retro_frame_time_callback {
1038 callback: Some(crate::frame_time_trampoline),
1039 reference: reference.as_micros(),
1040 };
1041 self.call_env(
1042 raw::RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK,
1043 (&mut callback as *mut raw::retro_frame_time_callback).cast::<c_void>(),
1044 )
1045 }
1046
1047 pub fn clear_frame_time_callback(&mut self) -> bool {
1048 self.call_env(
1049 raw::RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK,
1050 ptr::null_mut(),
1051 )
1052 }
1053
1054 pub fn message_interface_version(&mut self) -> Option<u32> {
1055 let mut version = 0u32;
1056 if self.call_env(
1057 raw::RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION,
1058 (&mut version as *mut u32).cast::<c_void>(),
1059 ) {
1060 Some(version)
1061 } else {
1062 None
1063 }
1064 }
1065
1066 pub fn set_message_ext(&mut self, message: ExtendedMessage) -> bool {
1067 let text = crate::sanitize_cstring(&message.message);
1068 let mut raw_message = raw::retro_message_ext {
1069 msg: text.as_ptr(),
1070 duration: message.duration_millis,
1071 priority: message.priority,
1072 level: message.level,
1073 target: message.target.as_raw(),
1074 type_: message.kind.as_raw(),
1075 progress: message.progress.as_raw(),
1076 };
1077 self.call_env(
1078 raw::RETRO_ENVIRONMENT_SET_MESSAGE_EXT,
1079 (&mut raw_message as *mut raw::retro_message_ext).cast::<c_void>(),
1080 )
1081 }
1082
1083 pub fn input_device_capabilities(&mut self) -> Option<InputDeviceCapabilities> {
1084 let mut capabilities = 0u64;
1085 if self.call_env(
1086 raw::RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES,
1087 (&mut capabilities as *mut u64).cast::<c_void>(),
1088 ) {
1089 Some(InputDeviceCapabilities::from_bits_truncate(capabilities))
1090 } else {
1091 None
1092 }
1093 }
1094
1095 pub fn supports_joypad_bitmasks(&mut self) -> bool {
1096 self.call_env(raw::RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, ptr::null_mut())
1097 }
1098
1099 pub fn set_keyboard_callback(&mut self) -> bool {
1100 let mut callback = raw::retro_keyboard_callback {
1101 callback: Some(crate::keyboard_event_trampoline),
1102 };
1103 self.call_env(
1104 raw::RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK,
1105 (&mut callback as *mut raw::retro_keyboard_callback).cast::<c_void>(),
1106 )
1107 }
1108
1109 pub fn set_input_descriptors(&mut self, descriptors: &[InputDescriptor]) -> bool {
1110 let mut descriptions = Vec::with_capacity(descriptors.len());
1111 let mut raw_descriptors = Vec::with_capacity(descriptors.len() + 1);
1112
1113 for descriptor in descriptors {
1114 let description = crate::sanitize_cstring(&descriptor.description);
1115 raw_descriptors.push(raw::retro_input_descriptor {
1116 port: descriptor.port.as_raw(),
1117 device: descriptor.device.as_raw(),
1118 index: descriptor.index.as_raw(),
1119 id: descriptor.id.as_raw(),
1120 description: description.as_ptr(),
1121 });
1122 descriptions.push(description);
1123 }
1124 raw_descriptors.push(raw::retro_input_descriptor::default());
1125
1126 let ok = self.call_env(
1127 raw::RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS,
1128 raw_descriptors.as_mut_ptr().cast::<c_void>(),
1129 );
1130 if ok {
1131 self.state.input_descriptors = Some(InputDescriptorStorage {
1132 _descriptions: descriptions,
1133 _raw: raw_descriptors,
1134 });
1135 }
1136 ok
1137 }
1138
1139 pub fn input_max_users(&mut self) -> Option<u32> {
1140 let mut users = 0u32;
1141 if self.call_env(
1142 raw::RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS,
1143 (&mut users as *mut u32).cast::<c_void>(),
1144 ) {
1145 Some(users)
1146 } else {
1147 None
1148 }
1149 }
1150
1151 pub fn led_interface(&mut self) -> Option<LedInterface> {
1152 let mut interface = raw::retro_led_interface::default();
1153 if self.call_env(
1154 raw::RETRO_ENVIRONMENT_GET_LED_INTERFACE,
1155 (&mut interface as *mut raw::retro_led_interface).cast::<c_void>(),
1156 ) {
1157 Some(LedInterface::from_raw(interface))
1158 } else {
1159 None
1160 }
1161 }
1162
1163 pub fn rumble_interface(&mut self) -> Option<RumbleInterface> {
1164 let mut interface = raw::retro_rumble_interface::default();
1165 if self.call_env(
1166 raw::RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE,
1167 (&mut interface as *mut raw::retro_rumble_interface).cast::<c_void>(),
1168 ) {
1169 Some(RumbleInterface::from_raw(interface))
1170 } else {
1171 None
1172 }
1173 }
1174
1175 pub fn device_power(&mut self) -> Option<DevicePower> {
1176 let mut power = raw::retro_device_power::default();
1177 if self.call_env(
1178 raw::RETRO_ENVIRONMENT_GET_DEVICE_POWER,
1179 (&mut power as *mut raw::retro_device_power).cast::<c_void>(),
1180 ) {
1181 Some(DevicePower::from_raw(power))
1182 } else {
1183 None
1184 }
1185 }
1186
1187 pub fn netplay_client_index(&mut self) -> Option<NetplayClientId> {
1188 let mut client_id = 0u32;
1189 if self.call_env(
1190 raw::RETRO_ENVIRONMENT_GET_NETPLAY_CLIENT_INDEX,
1191 (&mut client_id as *mut u32).cast::<c_void>(),
1192 ) {
1193 u16::try_from(client_id).ok().map(NetplayClientId::new)
1194 } else {
1195 None
1196 }
1197 }
1198
1199 pub fn sensor_interface(&mut self) -> Option<SensorInterface> {
1200 let mut interface = raw::retro_sensor_interface::default();
1201 if self.call_env(
1202 raw::RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE,
1203 (&mut interface as *mut raw::retro_sensor_interface).cast::<c_void>(),
1204 ) {
1205 Some(SensorInterface::from_raw(interface))
1206 } else {
1207 None
1208 }
1209 }
1210
1211 pub fn camera_interface(&mut self, request: CameraRequest) -> Option<CameraInterface> {
1212 let mut interface = raw::retro_camera_callback {
1213 caps: request.capabilities.bits(),
1214 width: request.size.width,
1215 height: request.size.height,
1216 ..raw::retro_camera_callback::default()
1217 };
1218 if !self.state.event_handlers.camera_raw_frame.is_empty() {
1219 interface.frame_raw_framebuffer = Some(crate::camera_frame_raw_trampoline);
1220 }
1221 if !self.state.event_handlers.camera_texture_frame.is_empty() {
1222 interface.frame_opengl_texture = Some(crate::camera_frame_opengl_texture_trampoline);
1223 }
1224 if !self.state.event_handlers.camera_initialized.is_empty() {
1225 interface.initialized = Some(crate::camera_initialized_trampoline);
1226 }
1227 if !self.state.event_handlers.camera_deinitialized.is_empty() {
1228 interface.deinitialized = Some(crate::camera_deinitialized_trampoline);
1229 }
1230 if self.call_env(
1231 raw::RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE,
1232 (&mut interface as *mut raw::retro_camera_callback).cast::<c_void>(),
1233 ) {
1234 Some(CameraInterface::from_raw(interface))
1235 } else {
1236 None
1237 }
1238 }
1239
1240 pub fn location_interface(&mut self) -> Option<LocationInterface> {
1241 let mut interface = raw::retro_location_callback::default();
1242 if !self.state.event_handlers.location_initialized.is_empty() {
1243 interface.initialized = Some(crate::location_initialized_trampoline);
1244 }
1245 if !self.state.event_handlers.location_deinitialized.is_empty() {
1246 interface.deinitialized = Some(crate::location_deinitialized_trampoline);
1247 }
1248 if self.call_env(
1249 raw::RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE,
1250 (&mut interface as *mut raw::retro_location_callback).cast::<c_void>(),
1251 ) {
1252 Some(LocationInterface::from_raw(interface))
1253 } else {
1254 None
1255 }
1256 }
1257
1258 pub fn microphone_interface(&mut self) -> Option<MicrophoneInterface> {
1259 let mut interface = raw::retro_microphone_interface {
1260 interface_version: raw::RETRO_MICROPHONE_INTERFACE_VERSION,
1261 ..raw::retro_microphone_interface::default()
1262 };
1263 if self.call_env(
1264 raw::RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE,
1265 (&mut interface as *mut raw::retro_microphone_interface).cast::<c_void>(),
1266 ) {
1267 Some(MicrophoneInterface::from_raw(interface))
1268 } else {
1269 None
1270 }
1271 }
1272
1273 pub fn midi_interface_available(&mut self) -> bool {
1274 self.call_env(raw::RETRO_ENVIRONMENT_GET_MIDI_INTERFACE, ptr::null_mut())
1275 }
1276
1277 pub fn midi_interface(&mut self) -> Option<MidiInterface> {
1278 let mut interface = raw::retro_midi_interface::default();
1279 if self.call_env(
1280 raw::RETRO_ENVIRONMENT_GET_MIDI_INTERFACE,
1281 (&mut interface as *mut raw::retro_midi_interface).cast::<c_void>(),
1282 ) {
1283 Some(MidiInterface::from_raw(interface))
1284 } else {
1285 None
1286 }
1287 }
1288
1289 pub fn set_serialization_quirks(
1290 &mut self,
1291 quirks: SerializationQuirks,
1292 ) -> Option<SerializationQuirks> {
1293 let mut raw_quirks = quirks.bits();
1294 if self.call_env(
1295 raw::RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS,
1296 (&mut raw_quirks as *mut u64).cast::<c_void>(),
1297 ) {
1298 Some(SerializationQuirks::from_bits_truncate(raw_quirks))
1299 } else {
1300 None
1301 }
1302 }
1303
1304 fn frontend_string(&mut self, command: u32) -> Option<String> {
1305 let mut value: *const c_char = ptr::null();
1306 let ok = self.call_env(command, (&mut value as *mut *const c_char).cast::<c_void>());
1307 if !ok || value.is_null() {
1308 return None;
1309 }
1310
1311 Some(
1314 unsafe { CStr::from_ptr(value) }
1315 .to_string_lossy()
1316 .into_owned(),
1317 )
1318 }
1319}
1320
1321#[cfg(test)]
1322mod tests {
1323 use super::{
1324 FastForwardRatio, Language, MessageProgress, RunLoopRateHz, ThrottleMode, VideoRotation,
1325 };
1326
1327 #[test]
1328 fn known_languages_round_trip_to_libretro_ids() {
1329 let languages = [
1330 Language::English,
1331 Language::Japanese,
1332 Language::French,
1333 Language::Spanish,
1334 Language::German,
1335 Language::Italian,
1336 Language::Dutch,
1337 Language::PortugueseBrazil,
1338 Language::PortuguesePortugal,
1339 Language::Russian,
1340 Language::Korean,
1341 Language::ChineseTraditional,
1342 Language::ChineseSimplified,
1343 Language::Esperanto,
1344 Language::Polish,
1345 Language::Vietnamese,
1346 Language::Arabic,
1347 Language::Greek,
1348 Language::Turkish,
1349 Language::Slovak,
1350 Language::Persian,
1351 Language::Hebrew,
1352 Language::Asturian,
1353 Language::Finnish,
1354 Language::Indonesian,
1355 Language::Swedish,
1356 Language::Ukrainian,
1357 Language::Czech,
1358 Language::CatalanValencia,
1359 Language::Catalan,
1360 Language::BritishEnglish,
1361 Language::Hungarian,
1362 Language::Belarusian,
1363 Language::Galician,
1364 Language::Norwegian,
1365 Language::Irish,
1366 ];
1367
1368 for language in languages {
1369 assert_eq!(Language::from_raw(language.as_raw()), language);
1370 }
1371 }
1372
1373 #[test]
1374 fn unknown_language_preserves_original_id() {
1375 assert_eq!(Language::from_raw(99), Language::Unknown(99));
1376 assert_eq!(Language::Unknown(99).as_raw(), 99);
1377 }
1378
1379 #[test]
1380 fn progress_percent_rejects_values_outside_libretro_range() {
1381 assert_eq!(
1382 MessageProgress::percent(100),
1383 Some(MessageProgress::Percent(100))
1384 );
1385 assert_eq!(MessageProgress::percent(101), None);
1386 }
1387
1388 #[test]
1389 fn video_rotations_encode_libretro_values() {
1390 assert_eq!(VideoRotation::Clockwise0.as_raw(), 0);
1391 assert_eq!(VideoRotation::CounterClockwise90.as_raw(), 1);
1392 assert_eq!(VideoRotation::CounterClockwise180.as_raw(), 2);
1393 assert_eq!(VideoRotation::CounterClockwise270.as_raw(), 3);
1394 }
1395
1396 #[test]
1397 fn throttle_modes_round_trip_to_libretro_ids() {
1398 let modes = [
1399 ThrottleMode::None,
1400 ThrottleMode::FrameStepping,
1401 ThrottleMode::FastForward,
1402 ThrottleMode::SlowMotion,
1403 ThrottleMode::Rewinding,
1404 ThrottleMode::Vsync,
1405 ThrottleMode::Unblocked,
1406 ];
1407
1408 for mode in modes {
1409 assert_eq!(ThrottleMode::from_raw(mode.as_raw()), mode);
1410 }
1411 }
1412
1413 #[test]
1414 fn throttle_rate_rejects_unknown_or_invalid_rates() {
1415 assert_eq!(RunLoopRateHz::new(60.0).map(RunLoopRateHz::get), Some(60.0));
1416 assert_eq!(RunLoopRateHz::new(0.0), None);
1417 assert_eq!(RunLoopRateHz::new(f32::NAN), None);
1418 }
1419
1420 #[test]
1421 fn fastforward_ratio_rejects_invalid_explicit_multipliers() {
1422 assert_eq!(
1423 FastForwardRatio::multiplier(2.0).map(FastForwardRatio::get),
1424 Some(2.0)
1425 );
1426 assert_eq!(FastForwardRatio::multiplier(1.0), None);
1427 assert_eq!(FastForwardRatio::multiplier(f32::INFINITY), None);
1428 assert_eq!(FastForwardRatio::multiplier(f32::NAN), None);
1429 }
1430}