diff --git a/core/src/cpu/arm9/bus/fallback.rs b/core/src/cpu/arm9/bus/fallback.rs index e5312f6..1676075 100644 --- a/core/src/cpu/arm9/bus/fallback.rs +++ b/core/src/cpu/arm9/bus/fallback.rs @@ -1316,12 +1316,7 @@ pub fn write_16(emu: &mut Emu, mut addr: u32, value } } - 0x05 => { - emu.gpu.vram.palette.write_le(addr as usize & 0x7FE, value); - if let Some(updates) = &mut emu.gpu.vram.bg_obj_updates { - updates.get_mut()[(addr >> 10 & 1) as usize].palette = true; - } - } + 0x05 => emu.gpu.vram.write_palette(addr & 0x7FE, value), 0x06 => match addr >> 21 & 7 { 0 => emu.gpu.vram.write_a_bg(addr, value), @@ -1331,12 +1326,7 @@ pub fn write_16(emu: &mut Emu, mut addr: u32, value _ => emu.gpu.vram.write_lcdc(addr, value), }, - 0x07 => { - emu.gpu.vram.oam.write_le(addr as usize & 0x7FE, value); - if let Some(updates) = &mut emu.gpu.vram.bg_obj_updates { - updates.get_mut()[(addr >> 10 & 1) as usize].oam = true; - } - } + 0x07 => emu.gpu.vram.write_oam(addr & 0x7FE, value), _ => { @@ -1667,12 +1657,7 @@ pub fn write_32(emu: &mut Emu, mut addr: u32, mut v } } - 0x05 => { - emu.gpu.vram.palette.write_le(addr as usize & 0x7FC, value); - if let Some(updates) = &mut emu.gpu.vram.bg_obj_updates { - updates.get_mut()[(addr >> 10 & 1) as usize].palette = true; - } - } + 0x05 => emu.gpu.vram.write_palette(addr & 0x7FC, value), 0x06 => match addr >> 21 & 7 { 0 => emu.gpu.vram.write_a_bg(addr, value), @@ -1682,12 +1667,7 @@ pub fn write_32(emu: &mut Emu, mut addr: u32, mut v _ => emu.gpu.vram.write_lcdc(addr, value), }, - 0x07 => { - emu.gpu.vram.oam.write_le(addr as usize & 0x7FC, value); - if let Some(updates) = &mut emu.gpu.vram.bg_obj_updates { - updates.get_mut()[(addr >> 10 & 1) as usize].oam = true; - } - } + 0x07 => emu.gpu.vram.write_oam(addr & 0x7FC, value), _ => { diff --git a/core/src/emu.rs b/core/src/emu.rs index 29b96a0..031627f 100644 --- a/core/src/emu.rs +++ b/core/src/emu.rs @@ -503,7 +503,7 @@ impl Emu { .write_control(swram::Control(3), &mut self.arm7, &mut self.arm9); self.gpu.write_power_control(gpu::PowerControl(0x820F)); - // –––––––––––––––– Game boot code –––––––––––––––– + // ––––––––––––––– Program boot code ––––––––––––––– let mut arm7_loaded_data = BoxedByteSlice::new_zeroed(header.arm7_size() as usize); self.ds_slot diff --git a/core/src/gpu/vram/access.rs b/core/src/gpu/vram/access.rs index 7577143..855b136 100644 --- a/core/src/gpu/vram/access.rs +++ b/core/src/gpu/vram/access.rs @@ -702,4 +702,26 @@ impl Vram { self.arm7.write_le_aligned_unchecked(addr, value); } } + + #[inline] + pub fn write_palette(&mut self, addr: u32, value: T) + where + [(); mem::size_of::()]: Sized, + { + self.palette.write_le(addr as usize, value); + if let Some(updates) = &mut self.bg_obj_updates { + updates.get_mut()[(addr >> 10 & 1) as usize].palette = true; + } + } + + #[inline] + pub fn write_oam(&mut self, addr: u32, value: T) + where + [(); mem::size_of::()]: Sized, + { + self.oam.write_le(addr as usize, value); + if let Some(updates) = &mut self.bg_obj_updates { + updates.get_mut()[(addr >> 10 & 1) as usize].oam = true; + } + } } diff --git a/frontend/desktop/src/debug_views.rs b/frontend/desktop/src/debug_views.rs index ec9cd07..e3cb274 100644 --- a/frontend/desktop/src/debug_views.rs +++ b/frontend/desktop/src/debug_views.rs @@ -24,6 +24,7 @@ pub type ViewKey = u32; pub trait FrameDataSlot<'a, T> { fn insert(self, value: T); + fn leave_unchanged(self); fn get_or_insert_with(self, f: impl FnOnce() -> T) -> &'a mut T; } @@ -38,6 +39,13 @@ impl<'a, T> FrameDataSlot<'a, T> for Entry<'a, ViewKey, T> { } } } + + fn leave_unchanged(self) { + if let Entry::Occupied(entry) = self { + entry.remove(); + } + } + fn get_or_insert_with(self, f: impl FnOnce() -> T) -> &'a mut T { self.or_insert_with(f) } @@ -47,100 +55,171 @@ impl<'a, T> FrameDataSlot<'a, T> for &'a mut Option { fn insert(self, value: T) { *self = Some(value); } + + fn leave_unchanged(self) { + *self = None; + } + fn get_or_insert_with(self, f: impl FnOnce() -> T) -> &'a mut T { Option::get_or_insert_with(self, f) } } pub trait Messages { - fn push_custom(&mut self, message: T::Message); + fn push(&mut self, message: ::Message); } -pub trait View: Sized { - const NAME: &'static str; - +pub trait EmuState: Sized { + type InitData: Send; + type Message: Send; type FrameData: Send; - type EmuState: Clone + Send; - type Message: Send = (); - fn new(window: &mut Window) -> Self; - fn destroy(self, window: &mut Window); + fn new(data: Self::InitData, visible: bool, emu: &mut Emu) -> Self; + fn destroy(self, _emu: &mut Emu) {} + + fn handle_visibility_changed(&mut self, _visible: bool, _emu: &mut Emu) {} + fn handle_message(&mut self, _message: Self::Message, _emu: &mut Emu); - fn emu_state(&self) -> Self::EmuState; - fn handle_emu_state_changed( - prev: Option<&Self::EmuState>, - new: Option<&Self::EmuState>, - emu: &mut Emu, - ); fn prepare_frame_data<'a, E: cpu::Engine, S: FrameDataSlot<'a, Self::FrameData>>( - emu_state: &Self::EmuState, + &mut self, emu: &mut Emu, frame_data: S, ); - fn handle_custom_message( - _message: Self::Message, - _emu_state: &Self::EmuState, - _emu: &mut Emu, - ) { +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RefreshType { + Addition, + Deletion, + VisibilityChange, + Message, +} + +pub trait InstanceableEmuState: EmuState { + const ADDITION_TRIGGERS_REFRESH: bool = false; + const DELETION_TRIGGERS_REFRESH: bool = false; + fn visibility_change_triggers_refresh(_visible: bool) -> bool { + false + } + fn message_triggers_refresh(_message: &Self::Message) -> bool { + false } - fn clear_frame_data(&mut self); - fn update_from_frame_data(&mut self, frame_data: &Self::FrameData, window: &mut Window); - fn customize_window<'ui, 'a, T: AsRef>( - &mut self, - ui: &imgui::Ui, - window: imgui::Window<'ui, 'a, T>, - ) -> imgui::Window<'ui, 'a, T>; - fn draw( + fn refresh(&mut self, _ty: RefreshType, _visible: bool, _emu: &mut Emu) {} + fn finish_preparing_frame_data(_emu: &mut Emu) {} +} + +pub trait BaseView: Sized { + const MENU_NAME: &'static str; + + fn new(window: &mut Window) -> Self; + fn destroy(self, _window: &mut Window) {} +} + +pub trait View: BaseView { + type EmuState: EmuState; + + fn emu_state(&self) -> ::InitData; + fn update_from_frame_data( &mut self, - ui: &imgui::Ui, + frame_data: &::FrameData, window: &mut Window, - emu_running: bool, - messages: impl Messages, - ) -> Option; + ); + + fn draw(&mut self, ui: &imgui::Ui, window: &mut Window, messages: impl Messages); +} + +pub trait StaticView: BaseView { + type Data: Send; + + fn fetch_data(emu: &mut Emu) -> Self::Data; + fn update_from_data(&mut self, data: Self::Data, window: &mut Window); + + fn draw(&mut self, ui: &imgui::Ui, window: &mut Window); +} + +pub trait SingletonView: BaseView { + fn window<'ui>( + &mut self, + ui: &'ui imgui::Ui, + ) -> imgui::Window<'ui, 'ui, impl AsRef + 'static> { + ui.window(Self::MENU_NAME) + } + fn window_stopped(ui: &'_ imgui::Ui) -> imgui::Window<'_, '_, impl AsRef + 'static> { + ui.window(Self::MENU_NAME) + } } -pub trait InstanceableView: View { - fn finish_preparing_frame_data(emu: &mut Emu); +pub trait InstanceableView: BaseView { + fn window<'ui>( + &mut self, + key: u32, + ui: &'ui imgui::Ui, + ) -> imgui::Window<'ui, 'ui, impl AsRef + 'static> { + ui.window(format!("{} {key}", Self::MENU_NAME)) + } + fn window_stopped( + key: u32, + ui: &'_ imgui::Ui, + ) -> imgui::Window<'_, '_, impl AsRef + 'static> { + ui.window(format!("{} {key}", Self::MENU_NAME)) + } } macro_rules! declare_structs { ( - $( - singleton + [$(( $s_view_ident: ident, $s_view_ty: ty, - $s_toggle_updates_message_ident: ident, - $s_update_emu_state_message_ident: ident - $(, $s_custom_message_ident: ident)? - );*$(;)? - $( - instanceable + $s_init_message_ident: ident, + $s_destroy_message_ident: ident, + $s_visibility_changed_message_ident: ident, + $s_message_ident: ident + )),*], + [$(( $i_view_ident: ident, $i_view_ty: ty, - $i_toggle_updates_message_ident: ident, - $i_update_emu_state_message_ident: ident - $(, $i_custom_message_ident: ident)? - );*$(;)? + $i_init_message_ident: ident, + $i_destroy_message_ident: ident, + $i_visibility_changed_message_ident: ident, + $i_message_ident: ident + )),*], + [$(( + $sst_view_ident: ident, + $sst_view_ty: ty, + $sst_fetch_message_ident: ident, + $sst_reply_message_ident: ident + )),*] ) => { pub enum Message { $( - $s_toggle_updates_message_ident(bool), - $s_update_emu_state_message_ident(Option<(<$s_view_ty as View>::EmuState, bool)>), - $($s_custom_message_ident(<$s_view_ty as View>::Message),)* + $s_init_message_ident(<<$s_view_ty as View>::EmuState as EmuState>::InitData, bool), + $s_destroy_message_ident, + $s_visibility_changed_message_ident(bool), + $s_message_ident(<<$s_view_ty as View>::EmuState as EmuState>::Message), )* $( - $i_toggle_updates_message_ident(ViewKey, bool), - $i_update_emu_state_message_ident( + $i_init_message_ident( ViewKey, - Option<(<$i_view_ty as View>::EmuState, bool)>, + <<$i_view_ty as View>::EmuState as EmuState>::InitData, + bool, ), - $($i_custom_message_ident(ViewKey, <$i_view_ty as View>::Message),)* + $i_destroy_message_ident(ViewKey), + $i_visibility_changed_message_ident(ViewKey, bool), + $i_message_ident(ViewKey, <<$i_view_ty as View>::EmuState as EmuState>::Message), + )* + $( + $sst_fetch_message_ident, )* } - #[derive(Clone)] - pub struct EmuState { + pub enum Notification { + $( + $sst_reply_message_ident(<$sst_view_ty as StaticView>::Data), + )* + } + + pub struct ViewsEmuState { $( $s_view_ident: Option<(<$s_view_ty as View>::EmuState, bool)>, )* @@ -149,9 +228,9 @@ macro_rules! declare_structs { )* } - impl EmuState { + impl ViewsEmuState { pub fn new() -> Self { - EmuState { + ViewsEmuState { $( $s_view_ident: None, )* @@ -161,129 +240,116 @@ macro_rules! declare_structs { } } - pub fn handle_message(&mut self, emu: &mut Emu, message: Message) { + pub fn handle_message( + &mut self, + emu: &mut Emu, + message: Message, + ) -> Option { match message { $( - Message::$s_toggle_updates_message_ident(enabled) => { - if let Some((state, view_enabled)) = &mut self.$s_view_ident { - *view_enabled = enabled; - if enabled { - <$s_view_ty>::handle_emu_state_changed( - None, - Some(&state), - emu, - ); - } else { - <$s_view_ty>::handle_emu_state_changed( - Some(&state), - None, - emu, - ); - } + Message::$s_init_message_ident(data, visible) => { + self.$s_view_ident = Some(( + <<$s_view_ty as View>::EmuState as EmuState>::new( + data, + visible, + emu, + ), + visible, + )); + } + Message::$s_destroy_message_ident => { + if let Some((state, _)) = self.$s_view_ident.take() { + state.destroy(emu); } } - Message::$s_update_emu_state_message_ident(new_state) => { - match self.$s_view_ident.as_ref() { - Some((prev_state, true)) => { - <$s_view_ty>::handle_emu_state_changed( - Some(prev_state), - new_state.as_ref().map(|s| &s.0), - emu, - ); - } - None => { - <$s_view_ty>::handle_emu_state_changed( - None, - new_state.as_ref().map(|s| &s.0), - emu, - ); - } - _ => {} + + Message::$s_visibility_changed_message_ident(new_visible) => { + if let Some((state, visible)) = &mut self.$s_view_ident { + *visible = new_visible; + state.handle_visibility_changed(new_visible, emu); } - self.$s_view_ident = new_state; } - $(Message::$s_custom_message_ident(message) => { + Message::$s_message_ident(message) => { if let Some((state, _)) = &mut self.$s_view_ident { - <$s_view_ty as View>::handle_custom_message(message, state, emu); + state.handle_message(message, emu); } - })* + } )* $( - Message::$i_toggle_updates_message_ident(key, enabled) => { - if let Some((state, view_enabled)) = self.$i_view_ident.get_mut(&key) { - *view_enabled = enabled; - if enabled { - <$i_view_ty>::handle_emu_state_changed( - None, - Some(state), - emu, - ); - } else { - <$i_view_ty>::handle_emu_state_changed( - Some(state), - None, + Message::$i_init_message_ident(key, data, visible) => { + self.$i_view_ident.insert( + key, + ( + <<$i_view_ty as View>::EmuState as EmuState>::new( + data, + visible, emu, - ); + ), + visible, + ), + ); + if <$i_view_ty as View>::EmuState::ADDITION_TRIGGERS_REFRESH { + for (other_key, (state, visible)) in &mut self.$i_view_ident { + if *other_key != key { + state.refresh(RefreshType::Addition, *visible, emu); + } } } - for (other_key, (state, view_enabled)) in &mut self.$i_view_ident { - if *other_key == key || !*view_enabled { - continue; + } + Message::$i_destroy_message_ident(key) => { + if let Some((state, _)) = self.$i_view_ident.remove(&key) { + state.destroy(emu); + } + if <$i_view_ty as View>::EmuState::DELETION_TRIGGERS_REFRESH { + for (other_key, (state, visible)) in &mut self.$i_view_ident { + if *other_key != key { + state.refresh(RefreshType::Deletion, *visible, emu); + } } - <$i_view_ty>::handle_emu_state_changed( - Some(state), - Some(state), - emu, - ); } } - Message::$i_update_emu_state_message_ident(key, new_state) => { - if let Some(new_state) = new_state { - match self.$i_view_ident.get(&key) { - Some((prev_state, true)) => { - <$i_view_ty>::handle_emu_state_changed( - Some(prev_state), - Some(&new_state.0), - emu, - ); - } - None => { - <$i_view_ty>::handle_emu_state_changed( - None, - Some(&new_state.0), - emu, - ); + + Message::$i_visibility_changed_message_ident(key, new_visible) => { + if let Some((state, visible)) = &mut self.$i_view_ident.get_mut(&key) { + *visible = new_visible; + state.handle_visibility_changed(new_visible, emu); + } + if <$i_view_ty as View>::EmuState::visibility_change_triggers_refresh( + new_visible, + ) { + for (other_key, (state, visible)) in &mut self.$i_view_ident { + if *other_key != key { + state.refresh(RefreshType::VisibilityChange, *visible, emu); } - _ => {} - } - self.$i_view_ident.insert(key, new_state); - } else { - if let Some((prev_state, true)) = self.$i_view_ident.remove(&key) { - <$i_view_ty>::handle_emu_state_changed( - Some(&prev_state), - None, - emu, - ); } } - for (other_key, (state, view_enabled)) in &self.$i_view_ident { - if *other_key == key || !*view_enabled { - continue; - } - <$i_view_ty>::handle_emu_state_changed( - Some(state), - Some(state), - emu, + } + Message::$i_message_ident(key, message) => { + let triggers_refresh = + <$i_view_ty as View>::EmuState::message_triggers_refresh( + &message, ); + if let Some((state, _)) = &mut self.$i_view_ident.get_mut(&key) { + state.handle_message(message, emu); } - } - $(Message::$i_custom_message_ident(key, message) => { - if let Some((state, _)) = self.$i_view_ident.get_mut(&key) { - <$i_view_ty as View>::handle_custom_message(message, state, emu); + if triggers_refresh { + for (other_key, (state, visible)) in &mut self.$i_view_ident { + if *other_key != key { + state.refresh(RefreshType::Message, *visible, emu); + } + } } - })* + } + )* + $( + Message::$sst_fetch_message_ident => { + return Some(Notification::$sst_reply_message_ident( + <$sst_view_ty as StaticView>::fetch_data(emu), + )); + } )* } + None } pub fn prepare_frame_data( @@ -292,13 +358,12 @@ macro_rules! declare_structs { frame_data: &mut FrameData, ) { $( - if let Some((emu_state, visible)) = &self.$s_view_ident { + if let Some((state, visible)) = &mut self.$s_view_ident { + let data = &mut frame_data.$s_view_ident; if *visible { - <$s_view_ty>::prepare_frame_data( - emu_state, - emu, - &mut frame_data.$s_view_ident, - ); + state.prepare_frame_data(emu, data); + } else { + data.leave_unchanged(); } } else { frame_data.$s_view_ident = None; @@ -306,27 +371,30 @@ macro_rules! declare_structs { )* $( frame_data.$i_view_ident.retain(|key, _| self.$i_view_ident.contains_key(key)); - for (key, (emu_state, visible)) in &self.$i_view_ident { - if !*visible { - continue; + for (key, (state, visible)) in &mut self.$i_view_ident { + let data = frame_data.$i_view_ident.entry(*key); + if *visible { + state.prepare_frame_data(emu, data); + } else { + data.leave_unchanged(); } - <$i_view_ty>::prepare_frame_data( - emu_state, - emu, - frame_data.$i_view_ident.entry(*key), - ); } - <$i_view_ty as InstanceableView>::finish_preparing_frame_data(emu); + < + <$i_view_ty as View>::EmuState as InstanceableEmuState + >::finish_preparing_frame_data(emu); )* } } pub struct FrameData { $( - $s_view_ident: Option<<$s_view_ty as View>::FrameData>, + $s_view_ident: Option<<<$s_view_ty as View>::EmuState as EmuState>::FrameData>, )* $( - $i_view_ident: HashMap::FrameData>, + $i_view_ident: HashMap< + ViewKey, + <<$i_view_ty as View>::EmuState as EmuState>::FrameData, + >, )* } @@ -356,10 +424,13 @@ macro_rules! declare_structs { pub struct UiState { messages: Vec, $( - $s_view_ident: Option<($s_view_ty, bool)>, + $s_view_ident: Option<(Option<$s_view_ty>, bool)>, )* $( - $i_view_ident: HashMap, + $i_view_ident: HashMap, bool)>, + )* + $( + $sst_view_ident: Option>, )* } @@ -374,24 +445,35 @@ macro_rules! declare_structs { $( $i_view_ident: HashMap::default(), )* + $( + $sst_view_ident: None, + )* + } + } + + pub fn handle_notif(&mut self, notif: Notification, window: &mut Window) { + match notif { + $( + Notification::$sst_reply_message_ident(data) => { + if let Some(Some(view)) = &mut self.$sst_view_ident { + view.update_from_data(data, window); + } + } + )* } } pub fn update_from_frame_data(&mut self, frame_data: &FrameData, window: &mut Window) { $( - if let Some((view, visible)) = &mut self.$s_view_ident { - if *visible { - if let Some(frame_data) = &frame_data.$s_view_ident { - view.update_from_frame_data(frame_data, window); - } + if let Some((Some(view), true)) = &mut self.$s_view_ident { + if let Some(frame_data) = &frame_data.$s_view_ident { + view.update_from_frame_data(frame_data, window); } } )* $( - for (key, (view, visible)) in &mut self.$i_view_ident { - if !*visible { - continue; - } + for (key, view) in &mut self.$i_view_ident { + let (Some(view), true) = view else { continue }; if let Some(frame_data) = frame_data.$i_view_ident.get(key) { view.update_from_frame_data(frame_data, window); } @@ -399,177 +481,253 @@ macro_rules! declare_structs { )* } - pub fn clear_frame_data(&mut self) { + pub fn emu_started(&mut self, window: &mut Window) { $( - if let Some((view, _)) = &mut self.$s_view_ident { - view.clear_frame_data(); + if let Some((view @ None, visible)) = &mut self.$s_view_ident { + let view = view.insert(<$s_view_ty>::new(window)); + let data = view.emu_state(); + self.messages.push(Message::$s_init_message_ident(data, *visible)); + } + )* + $( + for (key, view) in &mut self.$i_view_ident { + let (view @ None, visible) = view else { continue }; + let view = view.insert(<$i_view_ty>::new(window)); + let data = view.emu_state(); + self.messages.push(Message::$i_init_message_ident(*key, data, *visible)); } )* $( - for (view, _) in self.$i_view_ident.values_mut() { - view.clear_frame_data(); + if let Some(view @ None) = &mut self.$sst_view_ident { + *view = Some(<$sst_view_ty>::new(window)); + self.messages.push(Message::$sst_fetch_message_ident); } )* } - pub fn reload_emu_state(&mut self) { + pub fn emu_stopped(&mut self, window: &mut Window) { + $( + if let Some((view, _)) = &mut self.$s_view_ident { + if let Some(view) = view.take() { + view.destroy(window); + self.messages.push(Message::$s_destroy_message_ident); + }; + } + )* $( - if let Some((view, visible)) = &self.$s_view_ident { - let emu_state = view.emu_state(); - self.messages.push(Message::$s_update_emu_state_message_ident( - Some((emu_state, *visible)), - )); + for (key, (view, _)) in &mut self.$i_view_ident { + if let Some(view) = view.take() { + view.destroy(window); + self.messages.push(Message::$i_destroy_message_ident(*key)); + } } )* $( - for (key, (view, visible)) in &self.$i_view_ident { - let emu_state = view.emu_state(); - self.messages.push(Message::$i_update_emu_state_message_ident( - *key, - Some((emu_state, *visible)), - )); + if let Some(view) = &mut self.$sst_view_ident { + if let Some(view) = view.take() { + view.destroy(window); + }; } )* } - pub fn draw_menu(&mut self, ui: &imgui::Ui, window: &mut Window) { + pub fn draw_menu(&mut self, emu_running: bool, ui: &imgui::Ui, window: &mut Window) { + $( + if ui.menu_item_config(<$sst_view_ty>::MENU_NAME) + .selected(self.$sst_view_ident.is_some()) + .build() + { + if let Some(view) = self.$sst_view_ident.take() { + if let Some(view) = view { + view.destroy(window); + } + } else { + self.$sst_view_ident = Some(if emu_running { + self.messages.push(Message::$sst_fetch_message_ident); + Some(<$sst_view_ty>::new(window)) + } else { + None + }); + } + } + )* + ui.separator(); $( - if ui.menu_item_config(<$s_view_ty>::NAME) + if ui.menu_item_config(<$s_view_ty>::MENU_NAME) .selected(self.$s_view_ident.is_some()) - .build() { - if let Some(view) = self.$s_view_ident.take() { - self.messages.push(Message::$s_update_emu_state_message_ident( - None, - )); - view.0.destroy(window); + .build() + { + if let Some((view, _)) = self.$s_view_ident.take() { + if let Some(view) = view { + view.destroy(window); + self.messages.push(Message::$s_destroy_message_ident); + } } else { - let view = <$s_view_ty>::new(window); - let emu_state = view.emu_state(); - self.$s_view_ident = Some((view, true)); - self.messages.push(Message::$s_update_emu_state_message_ident( - Some((emu_state, true)), + self.$s_view_ident = Some(( + if emu_running { + let view = <$s_view_ty>::new(window); + let data = view.emu_state(); + self.messages.push(Message::$s_init_message_ident(data, true)); + Some(view) + } else { + None + }, + true, )); } } )* ui.separator(); $( - if ui.menu_item(<$i_view_ty>::NAME) { + if ui.menu_item(<$i_view_ty>::MENU_NAME) { let mut key = 1; while self.$i_view_ident.contains_key(&key) { key += 1; } - let view = <$i_view_ty>::new(window); - let emu_state = view.emu_state(); - self.$i_view_ident.insert(key, (view, true)); - self.messages.push(Message::$i_update_emu_state_message_ident( + self.$i_view_ident.insert( key, - Some((emu_state, true)), - )); + ( + if emu_running { + let view = <$i_view_ty>::new(window); + let data = view.emu_state(); + self.messages.push( + Message::$i_init_message_ident(key, data, true), + ); + Some(view) + } else { + None + }, + true, + ), + ); } )* } + fn draw_unavailable_view(ui: &imgui::Ui) { + ui.text("Start the emulator to see information."); + } + pub fn draw<'a>( &'a mut self, ui: &imgui::Ui, window: &mut Window, - emu_running: bool, ) -> impl Iterator + 'a { $( if let Some((view, visible)) = &mut self.$s_view_ident { let mut opened = true; let was_visible = *visible; - let mut new_emu_state = None; *visible = false; - view.customize_window( - ui, - ui.window(<$s_view_ty>::NAME).opened(&mut opened) - ).build(|| { - *visible = true; - new_emu_state = view.draw( - ui, - window, - emu_running, - &mut self.messages, - ); - }); - if let Some(new_emu_state) = new_emu_state { - self.messages.push(Message::$s_update_emu_state_message_ident( - Some((new_emu_state, true)) - )); - } else if !opened { - self.messages.push(Message::$s_update_emu_state_message_ident( - None, - )); - self.$s_view_ident.take().unwrap().0.destroy(window); - } else if was_visible != *visible { - self.messages.push(Message::$s_toggle_updates_message_ident( - *visible, - )); + if let Some(view) = view { + let ui_window = view.window(ui).opened(&mut opened); + ui_window.build(|| { + *visible = true; + view.draw(ui, window, &mut self.messages); + }); + if !opened { + let Some((Some(view), _)) = self.$s_view_ident.take() else { + unreachable!(); + }; + view.destroy(window); + self.messages.push(Message::$s_destroy_message_ident); + } else if was_visible != *visible { + self.messages.push(Message::$s_visibility_changed_message_ident( + *visible, + )); + } + } else { + <$s_view_ty>::window_stopped(ui) + .opened(&mut opened) + .build(|| { + *visible = true; + Self::draw_unavailable_view(ui); + }); + if !opened { + self.$s_view_ident.take(); + } } } )* $( - let closed_views: Vec<_> = self.$i_view_ident.extract_if( + let closed_views = self.$i_view_ident.extract_if( |key, (view, visible)| { let mut opened = true; let was_visible = *visible; - let mut new_emu_state = None; *visible = false; - view.customize_window( - ui, - ui.window(&format!("{} {key}", <$i_view_ty>::NAME)) - .opened(&mut opened), - ).build(|| { - *visible = true; - new_emu_state = view.draw( - ui, - window, - emu_running, - (&mut self.messages, *key), - ); - }); - if let Some(new_emu_state) = new_emu_state { - self.messages.push(Message::$i_update_emu_state_message_ident( - *key, - Some((new_emu_state, true)) - )); - } else if !opened { - self.messages.push(Message::$i_update_emu_state_message_ident( - *key, - None, - )); - return true; - } else if was_visible != *visible { - self.messages.push(Message::$i_toggle_updates_message_ident( - *key, - *visible, - )); + if let Some(view) = view { + view.window(*key, ui).opened(&mut opened).build(|| { + *visible = true; + view.draw(ui, window, (&mut self.messages, *key)); + }); + if !opened { + self.messages.push(Message::$i_destroy_message_ident(*key)); + return true; + } else if was_visible != *visible { + self.messages.push(Message::$i_visibility_changed_message_ident( + *key, + *visible, + )); + } + false + } else { + <$i_view_ty>::window_stopped(*key, ui) + .opened(&mut opened) + .build(|| { + *visible = true; + Self::draw_unavailable_view(ui); + }); + !opened } - false - } - ).map(|(_, (view, _))| view).collect(); + }, + ) + .filter_map(|(_, (view, _))| view) + .collect::>(); for view in closed_views { view.destroy(window); } )* + $( + if let Some(view) = &mut self.$sst_view_ident { + let mut opened = true; + if let Some(view) = view { + let ui_window = view.window(ui).opened(&mut opened); + ui_window.build(|| { + view.draw(ui, window); + }); + if !opened { + let Some(Some(view)) = self.$sst_view_ident.take() else { + unreachable!(); + }; + view.destroy(window); + } + } else { + <$sst_view_ty>::window_stopped(ui) + .opened(&mut opened) + .build(|| { + Self::draw_unavailable_view(ui); + }); + if !opened { + self.$sst_view_ident.take(); + } + } + } + )* self.messages.drain(..) } } $( impl Messages<$s_view_ty> for &mut Vec { - fn push_custom(&mut self, _message: <$s_view_ty as View>::Message) { - $(self.push(Message::$s_custom_message_ident(_message)))* + fn push(&mut self, message: <<$s_view_ty as View>::EmuState as EmuState>::Message) { + Vec::push(self, Message::$s_message_ident(message)); } } )* $( impl Messages<$i_view_ty> for (&mut Vec, ViewKey) { - fn push_custom(&mut self, _message: <$i_view_ty as View>::Message) { - $(self.0.push(Message::$i_custom_message_ident(self.1, _message)))* + fn push(&mut self, message: <<$i_view_ty as View>::EmuState as EmuState>::Message) { + self.0.push(Message::$i_message_ident(self.1, message)); } } )* @@ -577,13 +735,18 @@ macro_rules! declare_structs { } declare_structs!( - singleton arm7_state, CpuState, ToggleArm7State, UpdateArm7State, Arm7StateCustom; - singleton arm9_state, CpuState, ToggleArm9State, UpdateArm9State, Arm9StateCustom; - instanceable arm7_memory, CpuMemory, ToggleArm7Memory, UpdateArm7Memory, Arm7MemoryCustom; - instanceable arm9_memory, CpuMemory, ToggleArm9Memory, UpdateArm9Memory, Arm9MemoryCustom; - instanceable arm7_disasm, CpuDisasm, ToggleArm7Disasm, UpdateArm7Disasm; - instanceable arm9_disasm, CpuDisasm, ToggleArm9Disasm, UpdateArm9Disasm; - instanceable palettes_2d, Palettes2d, TogglePalettes2d, UpdatePalettes2d, Palettes2dCustom; - instanceable bg_maps_2d, BgMaps2d, ToggleBgMaps2d, UpdateBgMaps2d; - instanceable audio_channels, AudioChannels, ToggleAudioChannels, UpdateAudioChannels; + [ + (arm7_state, CpuState, InitArm7State, DestroyArm7State, Arm7StateVisibility, Arm7StateCustom), + (arm9_state, CpuState, InitArm9State, DestroyArm9State, Arm9StateVisibility, Arm9StateCustom) + ], + [ + (arm7_memory, CpuMemory, InitArm7Memory, DestroyArm7Memory, Arm7MemoryVisibility, Arm7MemoryCustom), + (arm9_memory, CpuMemory, InitArm9Memory, DestroyArm9Memory, Arm9MemoryVisibility, Arm9MemoryCustom), + (arm7_disasm, CpuDisasm, InitArm7Disasm, DestroyArm7Disasm, Arm7DisasmVisibility, Arm7DisasmCustom), + (arm9_disasm, CpuDisasm, InitArm9Disasm, DestroyArm9Disasm, Arm9DisasmVisibility, Arm9DisasmCustom), + (palettes_2d, Palettes2d, InitPalettes2d, DestroyPalettes2d, Palettes2dVisibility, Palettes2dCustom), + (bg_maps_2d, BgMaps2d, InitBgMaps2d, DestroyBgMaps2d, BgMaps2dVisibility, BgMaps2dCustom), + (audio_channels, AudioChannels, InitAudioChannels, DestroyAudioChannels, AudioChannelsVisibility, AudioChannelsCustom) + ], + [] ); diff --git a/frontend/desktop/src/debug_views/audio_channels.rs b/frontend/desktop/src/debug_views/audio_channels.rs index 0fcc0e6..435deaf 100644 --- a/frontend/desktop/src/debug_views/audio_channels.rs +++ b/frontend/desktop/src/debug_views/audio_channels.rs @@ -1,6 +1,6 @@ use super::{ common::regs::{bitfield, BitfieldCommand}, - FrameDataSlot, InstanceableView, Messages, View, + FrameDataSlot, BaseView, InstanceableEmuState, InstanceableView, Messages, RefreshType, View, }; use crate::ui::window::Window; use dust_core::{ @@ -8,7 +8,7 @@ use dust_core::{ cpu, emu::Emu, }; -use imgui::{PlotLines, SliderFlags, StyleVar, TableFlags, Ui}; +use imgui::{PlotLines, SliderFlags, StyleVar, TableFlags}; use realfft::{num_complex::Complex, RealFftPlanner as FftPlanner}; use std::cmp::Ordering; @@ -80,6 +80,78 @@ impl Default for ChannelData { } } +pub struct EmuState { + channel_index: ChannelIndex, +} + +impl super::EmuState for EmuState { + type InitData = ChannelIndex; + type Message = ChannelIndex; + type FrameData = (ChannelData, Vec); + + fn new(channel_index: Self::InitData, visible: bool, emu: &mut Emu) -> Self { + if visible { + emu.audio.channel_audio_capture_data.mask |= 1 << channel_index.get(); + } + EmuState { channel_index } + } + + fn destroy(self, emu: &mut Emu) { + emu.audio.channel_audio_capture_data.mask = 0; + } + + fn handle_visibility_changed(&mut self, visible: bool, emu: &mut Emu) { + if visible { + emu.audio.channel_audio_capture_data.mask |= 1 << self.channel_index.get(); + } else { + emu.audio.channel_audio_capture_data.mask = 0; + } + } + + fn handle_message(&mut self, channel_index: Self::Message, emu: &mut Emu) { + emu.audio.channel_audio_capture_data.mask = 1 << channel_index.get(); + self.channel_index = channel_index; + } + + fn prepare_frame_data<'a, E: cpu::Engine, S: FrameDataSlot<'a, Self::FrameData>>( + &mut self, + emu: &mut Emu, + frame_data: S, + ) { + let frame_data = frame_data.get_or_insert_with(|| (ChannelData::default(), Vec::new())); + frame_data.0.channel = Some(self.channel_index); + frame_data.1.clear(); + frame_data.1.extend_from_slice( + &emu.audio.channel_audio_capture_data.buffers[self.channel_index.get() as usize], + ); + let channel = &emu.audio.channels[self.channel_index.get() as usize]; + frame_data.0.control = channel.control(); + } +} + +impl InstanceableEmuState for EmuState { + const ADDITION_TRIGGERS_REFRESH: bool = false; + const DELETION_TRIGGERS_REFRESH: bool = true; + fn visibility_change_triggers_refresh(visible: bool) -> bool { + !visible + } + fn message_triggers_refresh(_message: &Self::Message) -> bool { + true + } + + fn refresh(&mut self, _ty: RefreshType, visible: bool, emu: &mut Emu) { + if visible { + emu.audio.channel_audio_capture_data.mask |= 1 << self.channel_index.get(); + } + } + + fn finish_preparing_frame_data(emu: &mut Emu) { + for buffer in &mut emu.audio.channel_audio_capture_data.buffers { + buffer.clear(); + } + } +} + pub struct AudioChannels { cur_channel: ChannelIndex, samples_to_show: u32, @@ -92,11 +164,8 @@ pub struct AudioChannels { fft_output_f32_buf: Vec, } -impl View for AudioChannels { - const NAME: &'static str = "Audio channels"; - - type FrameData = (ChannelData, Vec); - type EmuState = ChannelIndex; +impl BaseView for AudioChannels { + const MENU_NAME: &'static str = "Audio channels"; fn new(_window: &mut Window) -> Self { const DEFAULT_SAMPLES: u32 = 512 * 8; @@ -112,68 +181,27 @@ impl View for AudioChannels { fft_output_f32_buf: Vec::new(), } } +} - fn destroy(self, _window: &mut Window) {} +impl View for AudioChannels { + type EmuState = EmuState; - fn emu_state(&self) -> Self::EmuState { + fn emu_state(&self) -> ::InitData { self.cur_channel } - fn handle_emu_state_changed( - prev_channel_index: Option<&Self::EmuState>, - new_channel_index: Option<&Self::EmuState>, - emu: &mut Emu, - ) { - if let Some(prev_channel_index) = prev_channel_index { - emu.audio.channel_audio_capture_data.mask &= !(1 << prev_channel_index.get()); - } - if let Some(new_channel_index) = new_channel_index { - emu.audio.channel_audio_capture_data.mask |= 1 << new_channel_index.get(); - } - } - - fn prepare_frame_data<'a, E: cpu::Engine, S: FrameDataSlot<'a, Self::FrameData>>( - channel_index: &Self::EmuState, - emu: &mut Emu, - frame_data: S, + fn update_from_frame_data( + &mut self, + frame_data: &::FrameData, + _window: &mut Window, ) { - let frame_data = frame_data.get_or_insert_with(|| (ChannelData::default(), Vec::new())); - frame_data.0.channel = Some(*channel_index); - frame_data.1.clear(); - frame_data.1.extend_from_slice( - &emu.audio.channel_audio_capture_data.buffers[channel_index.get() as usize], - ); - let channel = &emu.audio.channels[channel_index.get() as usize]; - frame_data.0.control = channel.control(); - } - - fn clear_frame_data(&mut self) { - self.data.channel = None; - self.samples.fill(0.0); - } - - fn update_from_frame_data(&mut self, frame_data: &Self::FrameData, _window: &mut Window) { self.data.channel = frame_data.0.channel; self.samples .extend(frame_data.1.iter().map(|sample| *sample as f32 / 32768.0)); self.data.control = frame_data.0.control; } - fn customize_window<'ui, 'a, T: AsRef>( - &mut self, - _ui: &imgui::Ui, - window: imgui::Window<'ui, 'a, T>, - ) -> imgui::Window<'ui, 'a, T> { - window - } - - fn draw( - &mut self, - ui: &Ui, - window: &mut Window, - _emu_running: bool, - _messages: impl Messages, - ) -> Option { + fn draw(&mut self, ui: &imgui::Ui, window: &mut Window, mut messages: impl Messages) { let item_spacing = style!(ui, item_spacing); let sliders_width = 0.5 * (ui.content_region_avail()[0] - item_spacing[0]); @@ -185,15 +213,13 @@ impl View for AudioChannels { .display_format("Channel %d") .build(&mut raw_channel_index); - let new_state = if selection_updated { + if selection_updated { self.samples.fill(0.0); if let Some(channel_index) = ChannelIndex::new_checked(raw_channel_index) { self.cur_channel = channel_index; } - Some(self.cur_channel) - } else { - None - }; + messages.push(self.cur_channel); + } ui.same_line(); ui.set_next_item_width(sliders_width); @@ -208,7 +234,7 @@ impl View for AudioChannels { } if self.data.channel != Some(self.cur_channel) { - return new_state; + return; } PlotLines::new(ui, "##sample_graph", &self.samples.buffer) @@ -353,15 +379,7 @@ impl View for AudioChannels { ui.same_line(); ui.checkbox("##running", &mut self.data.control.running()); } - - new_state } } -impl InstanceableView for AudioChannels { - fn finish_preparing_frame_data(emu: &mut Emu) { - for buffer in &mut emu.audio.channel_audio_capture_data.buffers { - buffer.clear(); - } - } -} +impl InstanceableView for AudioChannels {} diff --git a/frontend/desktop/src/debug_views/bg_maps_2d.rs b/frontend/desktop/src/debug_views/bg_maps_2d.rs index 3c27916..5ae94bb 100644 --- a/frontend/desktop/src/debug_views/bg_maps_2d.rs +++ b/frontend/desktop/src/debug_views/bg_maps_2d.rs @@ -1,4 +1,6 @@ -use super::{common::rgb5_to_rgba8, FrameDataSlot, InstanceableView, Messages, View}; +use super::{ + common::rgb5_to_rgba8, BaseView, FrameDataSlot, InstanceableEmuState, InstanceableView, Messages, View, +}; use crate::ui::{ utils::{add2, combo_value, scale_to_fit, sub2s}, window::Window, @@ -12,7 +14,7 @@ use dust_core::{ }, utils::mem_prelude::*, }; -use imgui::{Image, MouseButton, SliderFlags, StyleColor, TextureId, Ui, WindowHoveredFlags}; +use imgui::{Image, MouseButton, SliderFlags, StyleColor, TextureId, WindowHoveredFlags}; use std::slice; #[derive(Clone, Copy, PartialEq, Eq)] @@ -95,72 +97,25 @@ impl Default for BgMapData { } } -pub struct BgMaps2d { - cur_selection: Selection, - tex_id: TextureId, - show_transparency_checkerboard: bool, - show_grid_lines: bool, - palette_buffer: Box<[u32; 0x1000]>, - pixel_buffer: Box<[u32; 1024 * 1024]>, - data: BgMapData, +pub struct EmuState { + selection: Selection, } -impl View for BgMaps2d { - const NAME: &'static str = "2D BG maps"; - +impl super::EmuState for EmuState { + type InitData = Selection; + type Message = Selection; type FrameData = BgMapData; - type EmuState = Selection; - fn new(window: &mut Window) -> Self { - let tex_id = window.imgui_gfx.create_and_add_owned_texture( - Some("BG map".into()), - imgui_wgpu::TextureDescriptor { - width: 1024, - height: 1024, - format: wgpu::TextureFormat::Rgba8Unorm, - ..Default::default() - }, - imgui_wgpu::SamplerDescriptor { - mag_filter: wgpu::FilterMode::Nearest, - min_filter: wgpu::FilterMode::Linear, - ..Default::default() - }, - ); - unsafe { - BgMaps2d { - cur_selection: Selection { - engine: Engine2d::A, - bg_index: BgIndex::new(0), - use_ext_palettes: None, - display_mode: None, - }, - tex_id, - show_transparency_checkerboard: true, - show_grid_lines: true, - palette_buffer: Box::new_zeroed().assume_init(), - pixel_buffer: Box::new_zeroed().assume_init(), - data: BgMapData::default(), - } - } + fn new(selection: Self::InitData, _visible: bool, _emu: &mut Emu) -> Self { + EmuState { selection } } - fn destroy(self, window: &mut Window) { - window.imgui_gfx.remove_texture(self.tex_id); - } - - fn emu_state(&self) -> Self::EmuState { - self.cur_selection - } - - fn handle_emu_state_changed( - _prev: Option<&Self::EmuState>, - _new: Option<&Self::EmuState>, - _emu: &mut Emu, - ) { + fn handle_message(&mut self, selection: Self::Message, _emu: &mut Emu) { + self.selection = selection; } fn prepare_frame_data<'a, E: cpu::Engine, S: FrameDataSlot<'a, Self::FrameData>>( - emu_state: &Self::EmuState, + &mut self, emu: &mut Emu, frame_data: S, ) { @@ -243,7 +198,7 @@ impl View for BgMaps2d { fn copy_bg_render_data( engine: &engine_2d::Engine2d, vram: &Vram, - selection: &Selection, + selection: Selection, data: &mut BgMapData, ) { let bg = &engine.bgs[selection.bg_index.get() as usize]; @@ -403,23 +358,89 @@ impl View for BgMaps2d { let frame_data = frame_data.get_or_insert_with(Default::default); frame_data.bgs[0] = get_bgs_data(&emu.gpu.engine_2d_a); frame_data.bgs[1] = get_bgs_data(&emu.gpu.engine_2d_b); - frame_data.selection = Some(*emu_state); - match emu_state.engine { - Engine2d::A => { - copy_bg_render_data(&emu.gpu.engine_2d_a, &emu.gpu.vram, emu_state, frame_data) - } - Engine2d::B => { - copy_bg_render_data(&emu.gpu.engine_2d_b, &emu.gpu.vram, emu_state, frame_data) + frame_data.selection = Some(self.selection); + match self.selection.engine { + Engine2d::A => copy_bg_render_data( + &emu.gpu.engine_2d_a, + &emu.gpu.vram, + self.selection, + frame_data, + ), + Engine2d::B => copy_bg_render_data( + &emu.gpu.engine_2d_b, + &emu.gpu.vram, + self.selection, + frame_data, + ), + } + } +} + +impl InstanceableEmuState for EmuState {} + +pub struct BgMaps2d { + cur_selection: Selection, + tex_id: TextureId, + show_transparency_checkerboard: bool, + show_grid_lines: bool, + palette_buffer: Box<[u32; 0x1000]>, + pixel_buffer: Box<[u32; 1024 * 1024]>, + data: BgMapData, +} + +impl BaseView for BgMaps2d { + const MENU_NAME: &'static str = "2D BG maps"; + + fn new(window: &mut Window) -> Self { + let tex_id = window.imgui_gfx.create_and_add_owned_texture( + Some("BG map".into()), + imgui_wgpu::TextureDescriptor { + width: 1024, + height: 1024, + format: wgpu::TextureFormat::Rgba8Unorm, + ..Default::default() + }, + imgui_wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }, + ); + unsafe { + BgMaps2d { + cur_selection: Selection { + engine: Engine2d::A, + bg_index: BgIndex::new(0), + use_ext_palettes: None, + display_mode: None, + }, + tex_id, + show_transparency_checkerboard: true, + show_grid_lines: true, + palette_buffer: Box::new_zeroed().assume_init(), + pixel_buffer: Box::new_zeroed().assume_init(), + data: BgMapData::default(), } } } - fn clear_frame_data(&mut self) { - self.data.bgs = BgMapData::default_bgs(); - self.data.selection = None; + fn destroy(self, window: &mut Window) { + window.imgui_gfx.remove_texture(self.tex_id); } +} - fn update_from_frame_data(&mut self, frame_data: &Self::FrameData, _window: &mut Window) { +impl View for BgMaps2d { + type EmuState = EmuState; + + fn emu_state(&self) -> ::InitData { + self.cur_selection + } + + fn update_from_frame_data( + &mut self, + frame_data: &::FrameData, + _window: &mut Window, + ) { self.data.bgs = frame_data.bgs; self.data.selection = frame_data.selection; self.data.cur_bg = frame_data.cur_bg; @@ -438,21 +459,7 @@ impl View for BgMaps2d { self.data.palette[..palette_len].copy_from_slice(&frame_data.palette[..palette_len]); } - fn customize_window<'ui, 'a, T: AsRef>( - &mut self, - _ui: &imgui::Ui, - window: imgui::Window<'ui, 'a, T>, - ) -> imgui::Window<'ui, 'a, T> { - window - } - - fn draw( - &mut self, - ui: &Ui, - window: &mut Window, - _emu_running: bool, - _messages: impl Messages, - ) -> Option { + fn draw(&mut self, ui: &imgui::Ui, window: &mut Window, mut messages: impl Messages) { if ui.is_window_hovered_with_flags(WindowHoveredFlags::ROOT_AND_CHILD_WINDOWS) && ui.is_mouse_clicked(MouseButton::Right) { @@ -569,11 +576,9 @@ impl View for BgMaps2d { }, ); - let new_state = if selection_updated { - Some(self.cur_selection) - } else { - None - }; + if selection_updated { + messages.push(self.cur_selection); + } if self.data.selection == Some(self.cur_selection) { ui.align_text_to_frame_padding(); @@ -598,7 +603,7 @@ impl View for BgMaps2d { }); if self.data.selection != Some(self.cur_selection) { - return new_state; + return; } let (mut image_pos, image_size) = scale_to_fit( @@ -881,11 +886,7 @@ impl View for BgMaps2d { ..Default::default() }, ); - - new_state } } -impl InstanceableView for BgMaps2d { - fn finish_preparing_frame_data(_emu: &mut Emu) {} -} +impl InstanceableView for BgMaps2d {} diff --git a/frontend/desktop/src/debug_views/cpu_disasm.rs b/frontend/desktop/src/debug_views/cpu_disasm.rs index dfc7550..36ba603 100644 --- a/frontend/desktop/src/debug_views/cpu_disasm.rs +++ b/frontend/desktop/src/debug_views/cpu_disasm.rs @@ -3,7 +3,7 @@ use super::{ disasm::{Addr, DisassemblyView}, RangeInclusive, }, - FrameDataSlot, InstanceableView, Messages, View, + FrameDataSlot, BaseView, InstanceableEmuState, InstanceableView, Messages, View, }; use crate::ui::window::Window; use dust_core::{ @@ -15,21 +15,6 @@ use dust_core::{ }; use imgui::StyleColor; -pub struct CpuDisasm { - view: DisassemblyView, - thumb: bool, - last_visible_addrs: RangeInclusive, - last_bytes_per_line: u8, - disasm_results: DisassemblyResults, -} - -#[derive(Clone)] -pub struct EmuState { - visible_addrs: RangeInclusive, - thumb: bool, -} - -#[derive(Clone)] pub struct DisassemblyResults { visible_addrs: RangeInclusive, cpu_pc: u32, @@ -38,53 +23,38 @@ pub struct DisassemblyResults { instrs: Vec, } -impl View for CpuDisasm { - const NAME: &'static str = if ARM9 { - "ARM9 disassembly" - } else { - "ARM7 disassembly" - }; +pub struct EmuState { + visible_addrs: RangeInclusive, + thumb: bool, +} +impl super::EmuState for EmuState { + type InitData = RangeInclusive; + type Message = (RangeInclusive, bool); type FrameData = DisassemblyResults; - type EmuState = EmuState; - fn new(_window: &mut Window) -> Self { - CpuDisasm { - view: DisassemblyView::new() - .bytes_per_line(4) - .show_range(false) - .addr_range((0, 0xFFFF_FFFF).into()), - last_visible_addrs: (0, 0).into(), - last_bytes_per_line: 4, - thumb: false, - disasm_results: DisassemblyResults { - visible_addrs: (0, 0).into(), - cpu_pc: 0, - cpu_thumb: false, - thumb: false, - instrs: Vec::new(), - }, - } - } - - fn destroy(self, _window: &mut Window) {} - - fn emu_state(&self) -> Self::EmuState { + fn new( + visible_addrs: Self::InitData, + _visible: bool, + _emu: &mut Emu, + ) -> Self { EmuState { - visible_addrs: self.last_visible_addrs, + visible_addrs, thumb: false, } } - fn handle_emu_state_changed( - _prev: Option<&Self::EmuState>, - _new: Option<&Self::EmuState>, + fn handle_message( + &mut self, + (visible_addrs, thumb): Self::Message, _emu: &mut Emu, ) { + self.visible_addrs = visible_addrs; + self.thumb = thumb; } fn prepare_frame_data<'a, E: cpu::Engine, S: FrameDataSlot<'a, Self::FrameData>>( - emu_state: &Self::EmuState, + &mut self, emu: &mut Emu, frame_data: S, ) { @@ -100,29 +70,72 @@ impl View for CpuDisasm { } else { (emu.arm7.r15(), emu.arm7.cpsr()) }; - frame_data.visible_addrs = emu_state.visible_addrs; + frame_data.visible_addrs = self.visible_addrs; frame_data.cpu_pc = r15; frame_data.cpu_thumb = cpsr.thumb_state(); - frame_data.thumb = emu_state.thumb; + frame_data.thumb = self.thumb; frame_data.instrs.clear(); disassemble_range::<_, ARM9>( emu, ( - emu_state.visible_addrs.start as u32, - emu_state.visible_addrs.end as u32, + self.visible_addrs.start as u32, + self.visible_addrs.end as u32, ), - emu_state.thumb, + self.thumb, &mut frame_data.instrs, ); } +} - fn clear_frame_data(&mut self) { - self.disasm_results.cpu_pc = 0; - self.disasm_results.cpu_thumb = false; - self.disasm_results.instrs.clear(); +impl InstanceableEmuState for EmuState {} + +pub struct CpuDisasm { + view: DisassemblyView, + thumb: bool, + last_visible_addrs: RangeInclusive, + last_bytes_per_line: u8, + disasm_results: DisassemblyResults, +} + +impl BaseView for CpuDisasm { + const MENU_NAME: &'static str = if ARM9 { + "ARM9 disassembly" + } else { + "ARM7 disassembly" + }; + + fn new(_window: &mut Window) -> Self { + CpuDisasm { + view: DisassemblyView::new() + .bytes_per_line(4) + .show_range(false) + .addr_range((0, 0xFFFF_FFFF).into()), + last_visible_addrs: (0, 0).into(), + last_bytes_per_line: 4, + thumb: false, + disasm_results: DisassemblyResults { + visible_addrs: (0, 0).into(), + cpu_pc: 0, + cpu_thumb: false, + thumb: false, + instrs: Vec::new(), + }, + } + } +} + +impl View for CpuDisasm { + type EmuState = EmuState; + + fn emu_state(&self) -> ::InitData { + self.last_visible_addrs } - fn update_from_frame_data(&mut self, frame_data: &Self::FrameData, _window: &mut Window) { + fn update_from_frame_data( + &mut self, + frame_data: &::FrameData, + _window: &mut Window, + ) { self.disasm_results.visible_addrs = frame_data.visible_addrs; self.disasm_results.cpu_pc = frame_data.cpu_pc; self.disasm_results.cpu_thumb = frame_data.cpu_thumb; @@ -133,21 +146,7 @@ impl View for CpuDisasm { .extend_from_slice(&frame_data.instrs); } - fn customize_window<'ui, 'a, T: AsRef>( - &mut self, - _ui: &imgui::Ui, - window: imgui::Window<'ui, 'a, T>, - ) -> imgui::Window<'ui, 'a, T> { - window - } - - fn draw( - &mut self, - ui: &imgui::Ui, - window: &mut Window, - _emu_running: bool, - _messages: impl Messages, - ) -> Option { + fn draw(&mut self, ui: &imgui::Ui, window: &mut Window, mut messages: impl Messages) { let mut emu_state_changed = false; let _mono_font = ui.push_font(window.imgui.mono_font); @@ -209,16 +208,9 @@ impl View for CpuDisasm { let visible_addrs = self.view.visible_addrs(1); if emu_state_changed || visible_addrs != self.last_visible_addrs { self.last_visible_addrs = visible_addrs; - Some(EmuState { - visible_addrs, - thumb: self.thumb, - }) - } else { - None + messages.push((visible_addrs, self.thumb)); } } } -impl InstanceableView for CpuDisasm { - fn finish_preparing_frame_data(_emu: &mut Emu) {} -} +impl InstanceableView for CpuDisasm {} diff --git a/frontend/desktop/src/debug_views/cpu_memory.rs b/frontend/desktop/src/debug_views/cpu_memory.rs index c96c7a5..1d751d8 100644 --- a/frontend/desktop/src/debug_views/cpu_memory.rs +++ b/frontend/desktop/src/debug_views/cpu_memory.rs @@ -1,4 +1,4 @@ -use super::{FrameDataSlot, InstanceableView, Messages, View}; +use super::{BaseView, FrameDataSlot, InstanceableEmuState, InstanceableView, Messages, View}; use crate::ui::window::Window; use dust_core::{ cpu::{self, arm7, arm9, bus}, @@ -6,61 +6,48 @@ use dust_core::{ }; use imgui_memory_editor::{Addr, MemoryEditor, RangeInclusive}; -pub struct CpuMemory { - editor: MemoryEditor, - last_visible_addrs: RangeInclusive, - mem_contents: MemContents, +pub struct MemContents { + visible_addrs: RangeInclusive, + data: Vec, } -#[derive(Clone)] -pub struct EmuState { - visible_addrs: RangeInclusive, +pub enum Message { + Write { addr: u32, value: u8 }, + UpdateVisibleAddrs(RangeInclusive), } -#[derive(Clone)] -pub struct MemContents { +pub struct EmuState { visible_addrs: RangeInclusive, - data: Vec, } -impl View for CpuMemory { - const NAME: &'static str = if ARM9 { "ARM9 memory" } else { "ARM7 memory" }; - +impl super::EmuState for EmuState { + type InitData = RangeInclusive; + type Message = Message; type FrameData = MemContents; - type EmuState = EmuState; - type Message = (u32, u8); - fn new(_window: &mut Window) -> Self { - let mut editor = MemoryEditor::new(); - editor.set_show_range(false); - editor.set_addr_range((0, 0xFFFF_FFFF).into()); - CpuMemory { - editor, - last_visible_addrs: (0, 0).into(), - mem_contents: MemContents { - visible_addrs: (0, 0).into(), - data: Vec::new(), - }, - } + fn new( + visible_addrs: Self::InitData, + _visible: bool, + _emu: &mut Emu, + ) -> Self { + EmuState { visible_addrs } } - fn destroy(self, _window: &mut Window) {} - - fn emu_state(&self) -> Self::EmuState { - EmuState { - visible_addrs: self.last_visible_addrs, + fn handle_message(&mut self, message: Self::Message, emu: &mut Emu) { + match message { + Message::Write { addr, value } => { + if ARM9 { + arm9::bus::write_8::(emu, addr, value); + } else { + arm7::bus::write_8::(emu, addr, value); + } + } + Message::UpdateVisibleAddrs(addrs) => self.visible_addrs = addrs, } } - fn handle_emu_state_changed( - _prev: Option<&Self::EmuState>, - _new: Option<&Self::EmuState>, - _emu: &mut Emu, - ) { - } - fn prepare_frame_data<'a, E: cpu::Engine, S: FrameDataSlot<'a, Self::FrameData>>( - emu_state: &Self::EmuState, + &mut self, emu: &mut Emu, frame_data: S, ) { @@ -71,55 +58,74 @@ impl View for CpuMemory { frame_data.data.clear(); frame_data .data - .reserve(((emu_state.visible_addrs.end - emu_state.visible_addrs.start) >> 2) as usize); - for addr in (emu_state.visible_addrs.start..=emu_state.visible_addrs.end).step_by(4) { + .reserve(((self.visible_addrs.end - self.visible_addrs.start) >> 2) as usize); + for addr in (self.visible_addrs.start..=self.visible_addrs.end).step_by(4) { frame_data.data.push(if ARM9 { arm9::bus::read_32::(emu, addr as u32) } else { arm7::bus::read_32::(emu, addr as u32) }); } - frame_data.visible_addrs = emu_state.visible_addrs; + frame_data.visible_addrs = self.visible_addrs; } +} - fn handle_custom_message( - (addr, value): Self::Message, - _emu_state: &Self::EmuState, - emu: &mut Emu, - ) { - if ARM9 { - arm9::bus::write_8::(emu, addr, value); - } else { - arm7::bus::write_8::(emu, addr, value); +impl InstanceableEmuState for EmuState {} + +pub struct CpuMemory { + editor: MemoryEditor, + last_visible_addrs: RangeInclusive, + mem_contents: MemContents, +} + +impl InstanceableView for CpuMemory { + fn window<'ui>( + &mut self, + key: u32, + ui: &'ui imgui::Ui, + ) -> imgui::Window<'ui, 'ui, impl AsRef + 'static> { + let width = self.editor.window_width(ui); + ui.window(format!("{} {key}", Self::MENU_NAME)) + .size_constraints([width, 0.0], [width, f32::INFINITY]) + } +} + +impl BaseView for CpuMemory { + const MENU_NAME: &'static str = if ARM9 { "ARM9 memory" } else { "ARM7 memory" }; + + fn new(_window: &mut Window) -> Self { + let mut editor = MemoryEditor::new(); + editor.set_show_range(false); + editor.set_addr_range((0, 0xFFFF_FFFF).into()); + CpuMemory { + editor, + last_visible_addrs: (0, 0).into(), + mem_contents: MemContents { + visible_addrs: (0, 0).into(), + data: Vec::new(), + }, } } +} - fn clear_frame_data(&mut self) { - self.mem_contents.data.clear(); +impl View for CpuMemory { + type EmuState = EmuState; + + fn emu_state(&self) -> ::InitData { + self.last_visible_addrs } - fn update_from_frame_data(&mut self, frame_data: &Self::FrameData, _window: &mut Window) { + fn update_from_frame_data( + &mut self, + frame_data: &::FrameData, + _window: &mut Window, + ) { self.mem_contents.data.clear(); self.mem_contents.data.extend_from_slice(&frame_data.data); self.mem_contents.visible_addrs = frame_data.visible_addrs; } - fn customize_window<'ui, 'a, T: AsRef>( - &mut self, - ui: &imgui::Ui, - window: imgui::Window<'ui, 'a, T>, - ) -> imgui::Window<'ui, 'a, T> { - let width = self.editor.window_width(ui); - window.size_constraints([width, 0.0], [width, f32::INFINITY]) - } - - fn draw( - &mut self, - ui: &imgui::Ui, - window: &mut Window, - _emu_running: bool, - mut messages: impl Messages, - ) -> Option { + fn draw(&mut self, ui: &imgui::Ui, window: &mut Window, mut messages: impl Messages) { let _mono_font = ui.push_font(window.imgui.mono_font); self.editor.handle_options_right_click(ui); @@ -140,7 +146,10 @@ impl View for CpuMemory { } }, |_, addr, value| { - messages.push_custom((addr as u32, value)); + messages.push(Message::Write { + addr: addr as u32, + value, + }); }, ); @@ -149,13 +158,7 @@ impl View for CpuMemory { visible_addrs.end = (visible_addrs.end + 3) & !3; if visible_addrs != self.last_visible_addrs { self.last_visible_addrs = visible_addrs; - Some(EmuState { visible_addrs }) - } else { - None + messages.push(Message::UpdateVisibleAddrs(visible_addrs)); } } } - -impl InstanceableView for CpuMemory { - fn finish_preparing_frame_data(_emu: &mut Emu) {} -} diff --git a/frontend/desktop/src/debug_views/cpu_state.rs b/frontend/desktop/src/debug_views/cpu_state.rs index 31ec0c7..41ee089 100644 --- a/frontend/desktop/src/debug_views/cpu_state.rs +++ b/frontend/desktop/src/debug_views/cpu_state.rs @@ -4,7 +4,7 @@ use super::{ regs::{bitfield, regs_32, regs_32_default_label, BitfieldCommand}, separator_with_width, }, - FrameDataSlot, Messages, View, + BaseView, FrameDataSlot, Messages, SingletonView, View, }; use crate::ui::{utils::combo_value, window::Window}; use dust_core::{ @@ -18,57 +18,26 @@ use dust_core::{ }; use imgui::{StyleColor, StyleVar, TableFlags}; -pub struct CpuState { - reg_values: Option<(Regs, Psr)>, - reg_bank: Option, -} - mod bounded { use dust_core::utils::bounded_int_lit; bounded_int_lit!(pub struct RegIndex(u8), max 15); } pub use bounded::*; -impl View for CpuState { - const NAME: &'static str = if ARM9 { "ARM9 state" } else { "ARM7 state" }; +pub struct EmuState; - type FrameData = (Regs, Psr); - type EmuState = (); +impl super::EmuState for EmuState { + type InitData = (); type Message = (Bank, RegIndex, u32); + type FrameData = (Regs, Psr); - fn new(_window: &mut Window) -> Self { - CpuState { - reg_values: None, - reg_bank: None, - } - } - - fn destroy(self, _window: &mut Window) {} - - fn emu_state(&self) -> Self::EmuState {} - - fn handle_emu_state_changed( - _prev: Option<&Self::EmuState>, - _new: Option<&Self::EmuState>, - _emu: &mut Emu, - ) { - } - - fn prepare_frame_data<'a, E: Engine, S: FrameDataSlot<'a, Self::FrameData>>( - _emu_state: &Self::EmuState, - emu: &mut Emu, - frame_data: S, - ) { - frame_data.insert(if ARM9 { - (emu.arm9.regs(), emu.arm9.cpsr()) - } else { - (emu.arm7.regs(), emu.arm7.cpsr()) - }); + fn new(_data: Self::InitData, _visible: bool, _emu: &mut Emu) -> Self { + EmuState } - fn handle_custom_message( + fn handle_message( + &mut self, (selected_bank, i, value): Self::Message, - _emu_state: &Self::EmuState, emu: &mut Emu, ) { let (mut regs, cpsr) = if ARM9 { @@ -114,29 +83,61 @@ impl View for CpuState { } } - fn clear_frame_data(&mut self) { - self.reg_values = None; + fn prepare_frame_data<'a, E: Engine, S: FrameDataSlot<'a, Self::FrameData>>( + &mut self, + emu: &mut Emu, + frame_data: S, + ) { + frame_data.insert(if ARM9 { + (emu.arm9.regs(), emu.arm9.cpsr()) + } else { + (emu.arm7.regs(), emu.arm7.cpsr()) + }); } +} - fn update_from_frame_data(&mut self, frame_data: &Self::FrameData, _window: &mut Window) { - self.reg_values = Some(frame_data.clone()); - } +pub struct CpuState { + reg_values: Option<(Regs, Psr)>, + reg_bank: Option, +} - fn customize_window<'ui, 'a, T: AsRef>( +impl SingletonView for CpuState { + fn window<'ui>( &mut self, - _ui: &imgui::Ui, - window: imgui::Window<'ui, 'a, T>, - ) -> imgui::Window<'ui, 'a, T> { - window.always_auto_resize(true) + ui: &'ui imgui::Ui, + ) -> imgui::Window<'ui, 'ui, impl AsRef + 'static> { + ui.window(Self::MENU_NAME).always_auto_resize(true) + } + fn window_stopped(ui: &'_ imgui::Ui) -> imgui::Window<'_, '_, impl AsRef + 'static> { + ui.window(Self::MENU_NAME).always_auto_resize(true) + } +} + +impl BaseView for CpuState { + const MENU_NAME: &'static str = if ARM9 { "ARM9 state" } else { "ARM7 state" }; + + fn new(_window: &mut Window) -> Self { + CpuState { + reg_values: None, + reg_bank: None, + } } +} + +impl View for CpuState { + type EmuState = EmuState; + + fn emu_state(&self) -> ::InitData {} - fn draw( + fn update_from_frame_data( &mut self, - ui: &imgui::Ui, - window: &mut Window, - _emu_running: bool, - mut messages: impl Messages, - ) -> Option { + frame_data: &::FrameData, + _window: &mut Window, + ) { + self.reg_values = Some(frame_data.clone()); + } + + fn draw(&mut self, ui: &imgui::Ui, window: &mut Window, mut messages: impl Messages) { if let Some((reg_values, cpsr)) = self.reg_values.as_mut() { let _mono_font_token = ui.push_font(window.imgui.mono_font); let _item_spacing = @@ -160,7 +161,7 @@ impl View for CpuState { 0, ®_values.gprs, |i, value| { - messages.push_custom((cpu_reg_bank, RegIndex::new(i as u8), value)); + messages.push((cpu_reg_bank, RegIndex::new(i as u8), value)); }, regs_32_default_label, |i| { @@ -364,7 +365,7 @@ impl View for CpuState { 8, r8_r12, |i, value| { - messages.push_custom((reg_bank, RegIndex::new(i as u8), value)); + messages.push((reg_bank, RegIndex::new(i as u8), value)); }, regs_32_label, |i| { @@ -390,7 +391,7 @@ impl View for CpuState { 13, r13_14, |i, value| { - messages.push_custom((reg_bank, RegIndex::new(i as u8), value)); + messages.push((reg_bank, RegIndex::new(i as u8), value)); }, regs_32_label, |i| { @@ -425,6 +426,5 @@ impl View for CpuState { }); } } - None } } diff --git a/frontend/desktop/src/debug_views/palettes_2d.rs b/frontend/desktop/src/debug_views/palettes_2d.rs index 90284f5..29c1b7b 100644 --- a/frontend/desktop/src/debug_views/palettes_2d.rs +++ b/frontend/desktop/src/debug_views/palettes_2d.rs @@ -1,6 +1,6 @@ use super::{ common::{rgb32f_to_rgb5, rgb5_to_rgb32f, rgb5_to_rgba32f}, - FrameDataSlot, InstanceableView, Messages, View, + BaseView, FrameDataSlot, InstanceableEmuState, InstanceableView, Messages, View, }; use crate::ui::{utils::combo_value, window::Window}; use dust_core::{cpu, emu::Emu, utils::mem_prelude::*}; @@ -54,67 +54,85 @@ impl Default for PaletteData { } } -pub struct Palettes2d { - cur_selection: Selection, - data: PaletteData, - cur_color_index: u16, - cur_color: [f32; 3], +pub enum Message { + Write { + selection: Selection, + index: u16, + value: u16, + }, + UpdateSelection(Selection), } -impl View for Palettes2d { - const NAME: &'static str = "2D engine palettes"; +pub struct EmuState { + selection: Selection, +} +impl super::EmuState for EmuState { + type InitData = Selection; + type Message = Message; type FrameData = PaletteData; - type EmuState = Selection; - type Message = (Selection, u16, u16); - fn new(_window: &mut Window) -> Self { - Palettes2d { - cur_selection: Selection { - engine: Engine2d::A, - palette: Palette::Bg, - }, - data: PaletteData::default(), - cur_color_index: 0, - cur_color: [0.0; 3], - } + fn new(selection: Self::InitData, _visible: bool, _emu: &mut Emu) -> Self { + EmuState { selection } } - fn destroy(self, _window: &mut Window) {} + fn handle_message(&mut self, message: Self::Message, emu: &mut Emu) { + match message { + Message::Write { + selection, + index, + value, + } => match selection.palette { + Palette::Bg => { + let base = ((selection.engine == Engine2d::B) as u32) << 10; + emu.gpu + .vram + .write_palette(base | ((index as u32) << 1), value); + } - fn emu_state(&self) -> Self::EmuState { - self.cur_selection - } + Palette::Obj => { + let base = ((selection.engine == Engine2d::B) as u32) << 10 | 0x200; + emu.gpu + .vram + .write_palette(base | ((index as u32) << 1), value); + } - fn handle_emu_state_changed( - _prev: Option<&Self::EmuState>, - _new: Option<&Self::EmuState>, - _emu: &mut Emu, - ) { + Palette::ExtBg => match selection.engine { + Engine2d::A => emu.gpu.vram.write_a_bg_ext_pal((index as u32) << 1, value), + Engine2d::B => emu.gpu.vram.write_b_bg_ext_pal((index as u32) << 1, value), + }, + + Palette::ExtObj => match selection.engine { + Engine2d::A => emu.gpu.vram.write_a_obj_ext_pal((index as u32) << 1, value), + Engine2d::B => emu.gpu.vram.write_b_obj_ext_pal((index as u32) << 1, value), + }, + }, + Message::UpdateSelection(selection) => self.selection = selection, + } } fn prepare_frame_data<'a, E: cpu::Engine, S: FrameDataSlot<'a, Self::FrameData>>( - emu_state: &Self::EmuState, + &mut self, emu: &mut Emu, frame_data: S, ) { let palette_data = frame_data.get_or_insert_with(Default::default); - palette_data.selection = Some(*emu_state); + palette_data.selection = Some(self.selection); unsafe { - match emu_state.palette { + match self.selection.palette { Palette::Bg => { - let base = ((emu_state.engine == Engine2d::B) as usize) << 10; + let base = ((self.selection.engine == Engine2d::B) as usize) << 10; palette_data.data[..0x200] .copy_from_slice(&emu.gpu.vram.palette.as_arr()[base..base + 0x200]); } Palette::Obj => { - let base = ((emu_state.engine == Engine2d::B) as usize) << 10 | 0x200; + let base = ((self.selection.engine == Engine2d::B) as usize) << 10 | 0x200; palette_data.data[..0x200] .copy_from_slice(&emu.gpu.vram.palette.as_arr()[base..base + 0x200]); } - Palette::ExtBg => match emu_state.engine { + Palette::ExtBg => match self.selection.engine { Engine2d::A => emu.gpu.vram.read_a_bg_ext_pal_slice( 0, 0x8000, @@ -127,7 +145,7 @@ impl View for Palettes2d { ), }, - Palette::ExtObj => match emu_state.engine { + Palette::ExtObj => match self.selection.engine { Engine2d::A => emu.gpu.vram.read_a_obj_ext_pal_slice( 0, 0x2000, @@ -142,66 +160,51 @@ impl View for Palettes2d { } } } +} - fn handle_custom_message( - (selection, index, value): Self::Message, - _emu_state: &Self::EmuState, - emu: &mut Emu, - ) { - match selection.palette { - Palette::Bg => { - let base = ((selection.engine == Engine2d::B) as usize) << 10; - emu.gpu - .vram - .palette - .write_le(base | ((index as usize) << 1), value); - } +impl InstanceableEmuState for EmuState {} - Palette::Obj => { - let base = ((selection.engine == Engine2d::B) as usize) << 10 | 0x200; - emu.gpu - .vram - .palette - .write_le(base | ((index as usize) << 1), value); - } +pub struct Palettes2d { + cur_selection: Selection, + data: PaletteData, + cur_color_index: u16, + cur_color: [f32; 3], +} - Palette::ExtBg => match selection.engine { - Engine2d::A => emu.gpu.vram.write_a_bg_ext_pal((index as u32) << 1, value), - Engine2d::B => emu.gpu.vram.write_b_bg_ext_pal((index as u32) << 1, value), - }, +impl BaseView for Palettes2d { + const MENU_NAME: &'static str = "2D engine palettes"; - Palette::ExtObj => match selection.engine { - Engine2d::A => emu.gpu.vram.write_a_obj_ext_pal((index as u32) << 1, value), - Engine2d::B => emu.gpu.vram.write_b_obj_ext_pal((index as u32) << 1, value), + fn new(_window: &mut Window) -> Self { + Palettes2d { + cur_selection: Selection { + engine: Engine2d::A, + palette: Palette::Bg, }, + data: PaletteData::default(), + cur_color_index: 0, + cur_color: [0.0; 3], } } +} + +impl View for Palettes2d { + type EmuState = EmuState; - fn clear_frame_data(&mut self) { - self.data.selection = None; + fn emu_state(&self) -> ::InitData { + self.cur_selection } - fn update_from_frame_data(&mut self, frame_data: &Self::FrameData, _window: &mut Window) { + fn update_from_frame_data( + &mut self, + frame_data: &::FrameData, + _window: &mut Window, + ) { self.data.selection = frame_data.selection; let data_len = frame_data.selection.unwrap().data_len(); self.data.data[..data_len].copy_from_slice(&frame_data.data[..data_len]); } - fn customize_window<'ui, 'a, T: AsRef>( - &mut self, - _ui: &imgui::Ui, - window: imgui::Window<'ui, 'a, T>, - ) -> imgui::Window<'ui, 'a, T> { - window - } - - fn draw( - &mut self, - ui: &Ui, - _window: &mut Window, - _emu_running: bool, - mut messages: impl Messages, - ) -> Option { + fn draw(&mut self, ui: &imgui::Ui, _window: &mut Window, mut messages: impl Messages) { static POSSIBLE_SELECTIONS: [Selection; 8] = [ Selection::new(Engine2d::A, Palette::Bg), Selection::new(Engine2d::A, Palette::Obj), @@ -235,14 +238,12 @@ impl View for Palettes2d { }, ); - let new_state = if selection_updated { - Some(self.cur_selection) - } else { - None - }; + if selection_updated { + messages.push(Message::UpdateSelection(self.cur_selection)); + } if self.data.selection != Some(self.cur_selection) { - return new_state; + return; } let _frame_rounding = ui.push_style_var(StyleVar::FrameRounding(1.0)); @@ -306,15 +307,15 @@ impl View for Palettes2d { .alpha(false) .build() { - messages.push_custom((self.cur_selection, i, rgb32f_to_rgb5(self.cur_color))) + messages.push(Message::Write { + selection: self.cur_selection, + index: i, + value: rgb32f_to_rgb5(self.cur_color), + }); } }); } - - new_state } } -impl InstanceableView for Palettes2d { - fn finish_preparing_frame_data(_emu: &mut Emu) {} -} +impl InstanceableView for Palettes2d {} diff --git a/frontend/desktop/src/emu.rs b/frontend/desktop/src/emu.rs index bd9a3ff..9b90ddb 100644 --- a/frontend/desktop/src/emu.rs +++ b/frontend/desktop/src/emu.rs @@ -110,6 +110,9 @@ pub enum Message { pub enum Notification { Stopped, + #[cfg(feature = "debug-views")] + DebugViews(debug_views::Notification), + RtcTimeOffsetSecondsUpdated(i64), SavestateCreated(String, Savestate), SavestateFailed(String), @@ -474,7 +477,7 @@ pub(super) fn run( let mut last_save_flush_time = last_frame_time; #[cfg(feature = "debug-views")] - let mut debug_views = debug_views::EmuState::new(); + let mut debug_views = debug_views::ViewsEmuState::new(); #[cfg(feature = "gdb-server")] let mut gdb_server = None; @@ -514,7 +517,9 @@ pub(super) fn run( #[cfg(feature = "debug-views")] Message::DebugViews(message) => { - debug_views.handle_message(&mut emu, message); + if let Some(notif) = debug_views.handle_message(&mut emu, message) { + notif!(Notification::DebugViews(notif)); + } } Message::Reset => { diff --git a/frontend/desktop/src/ui.rs b/frontend/desktop/src/ui.rs index c7634b6..6390a9b 100644 --- a/frontend/desktop/src/ui.rs +++ b/frontend/desktop/src/ui.rs @@ -245,7 +245,12 @@ impl UiState { } impl UiState { - fn load_from_rom_path(&mut self, path: &Path, config: &mut Config, window: &window::Window) { + fn load_from_rom_path( + &mut self, + path: &Path, + config: &mut Config, + window: &mut window::Window, + ) { let Some(game_title) = path.file_stem().and_then(|path| path.to_str()) else { error!("Invalid ROM path", "Invalid ROM path provided: {path:?}"); return; @@ -313,7 +318,7 @@ impl UiState { } } - fn load_firmware(&mut self, config: &mut Config, window: &window::Window) { + fn load_firmware(&mut self, config: &mut Config, window: &mut window::Window) { self.stop(config, window); match config::Launch::new(&config.config, true) { Ok((launch_config, warnings)) => { @@ -461,7 +466,7 @@ impl UiState { save_path: Option, title: String, ds_slot_rom: Option<(DsSlotRom, &Path)>, - window: &window::Window, + window: &mut window::Window, ) { #[cfg(feature = "discord-presence")] if let Some(presence) = &mut self.discord_presence { @@ -636,7 +641,7 @@ impl UiState { .expect("couldn't spawn emulation thread"); #[cfg(feature = "debug-views")] - self.debug_views.reload_emu_state(); + self.debug_views.emu_started(window); self.emu = Some(EmuState { playing, @@ -679,7 +684,7 @@ impl UiState { } } - fn stop(&mut self, config: &mut Config, window: &window::Window) { + fn stop(&mut self, config: &mut Config, window: &mut window::Window) { self.stop_emu(config); self.savestate_editor @@ -692,7 +697,7 @@ impl UiState { config.config.unset_game(); #[cfg(feature = "debug-views")] - self.debug_views.clear_frame_data(); + self.debug_views.emu_stopped(window); triple_buffer::reset( ( @@ -1208,6 +1213,11 @@ pub fn main() { continue 'process_notifs; } + #[cfg(feature = "debug-views")] + emu::Notification::DebugViews(notif) => { + state.debug_views.handle_notif(notif, window); + } + emu::Notification::RtcTimeOffsetSecondsUpdated(value) => { set_config!(config.config, rtc_time_offset_seconds, value); config.config.rtc_time_offset_seconds.clear_updates(); @@ -1499,7 +1509,7 @@ pub fn main() { #[cfg(feature = "debug-views")] section! {{ - state.debug_views.draw_menu(ui, window); + state.debug_views.draw_menu(state.emu.is_some(), ui, window); }} }); } @@ -1539,7 +1549,7 @@ pub fn main() { // Draw debug views #[cfg(feature = "debug-views")] - for message in state.debug_views.draw(ui, window, state.emu.is_some()) { + for message in state.debug_views.draw(ui, window) { if let Some(emu) = &state.emu { emu.send_message(emu::Message::DebugViews(message)); } diff --git a/frontend/desktop/src/ui/savestate_editor.rs b/frontend/desktop/src/ui/savestate_editor.rs index 06d3aed..ceab01f 100644 --- a/frontend/desktop/src/ui/savestate_editor.rs +++ b/frontend/desktop/src/ui/savestate_editor.rs @@ -293,9 +293,7 @@ impl Editor { }; let mut warnings = Vec::new(); for entry in dir_entries { - let Ok(entry) = entry else { - continue; - }; + let Ok(entry) = entry else { continue }; let path = entry.path(); if path.extension() != Some("state".as_ref()) { continue;