use crate::event_queue::EventQueue;
use crate::guest_call::SharedGuestCallStack;
use crate::menu_manager::{ProcessMenuTrackingState, SharedNativeMenuSelection};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PendingHandleByteReplacement {
pub(crate) handle: u32,
pub(crate) expected_ptr: u32,
pub(crate) replacement: Vec<u8>,
}
#[derive(Debug, Default)]
pub(crate) struct ProcessContext {
event_queue: EventQueue,
menu_tracking: Option<ProcessMenuTrackingState>,
pending_native_menu_selection: SharedNativeMenuSelection,
guest_calls: SharedGuestCallStack,
pending_memory_effects: Vec<PendingHandleByteReplacement>,
}
impl ProcessContext {
pub(crate) fn event_queue(&self) -> &EventQueue {
&self.event_queue
}
pub(crate) fn event_queue_mut(&mut self) -> &mut EventQueue {
&mut self.event_queue
}
pub(crate) fn menu_tracking(&self) -> Option<&ProcessMenuTrackingState> {
self.menu_tracking.as_ref()
}
#[cfg(test)]
pub(crate) fn menu_tracking_mut(&mut self) -> Option<&mut ProcessMenuTrackingState> {
self.menu_tracking.as_mut()
}
#[cfg(test)]
pub(crate) fn take_menu_tracking(&mut self) -> Option<ProcessMenuTrackingState> {
self.menu_tracking.take()
}
#[cfg(test)]
pub(crate) fn set_menu_tracking(&mut self, state: Option<ProcessMenuTrackingState>) {
self.menu_tracking = state;
}
#[cfg(test)]
pub(crate) fn menu_tracking_slot_mut(&mut self) -> &mut Option<ProcessMenuTrackingState> {
&mut self.menu_tracking
}
#[cfg(test)]
pub(crate) fn pending_memory_effects(&self) -> &[PendingHandleByteReplacement] {
&self.pending_memory_effects
}
#[cfg(test)]
pub(crate) fn pending_memory_effects_mut(&mut self) -> &mut Vec<PendingHandleByteReplacement> {
&mut self.pending_memory_effects
}
pub(crate) fn take_pending_memory_effects(&mut self) -> Vec<PendingHandleByteReplacement> {
std::mem::take(&mut self.pending_memory_effects)
}
pub(crate) fn has_pending_memory_effects(&self) -> bool {
!self.pending_memory_effects.is_empty()
}
pub(crate) fn adopt_menu_tracking(&mut self, adapter: &mut Option<ProcessMenuTrackingState>) {
assert!(
adapter.is_none() || self.menu_tracking.is_none(),
"cannot attach two active Menu Manager continuations"
);
if self.menu_tracking.is_none() {
self.menu_tracking = adapter.take();
}
}
pub(crate) fn event_queue_and_menu_tracking_mut(
&mut self,
) -> (&mut EventQueue, &mut Option<ProcessMenuTrackingState>) {
(&mut self.event_queue, &mut self.menu_tracking)
}
pub(crate) fn event_queue_menu_tracking_and_memory_effects_mut(
&mut self,
) -> (
&mut EventQueue,
&mut Option<ProcessMenuTrackingState>,
&mut Vec<PendingHandleByteReplacement>,
) {
(
&mut self.event_queue,
&mut self.menu_tracking,
&mut self.pending_memory_effects,
)
}
pub(crate) fn attach_native_menu_selection(&self, adapter: &mut SharedNativeMenuSelection) {
adapter.attach_to(&self.pending_native_menu_selection);
}
pub(crate) fn attach_guest_calls(&self, adapter: &mut SharedGuestCallStack) {
adapter.attach_to(&self.guest_calls);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event_queue::QueuedEvent;
use crate::guest_call::GuestCallTarget;
use crate::guest_procedure::GuestIsa;
#[test]
fn process_context_owns_canonical_event_queue() {
let mut context = ProcessContext::default();
assert!(context.event_queue().is_empty());
context.event_queue_mut().push_back(QueuedEvent {
what: 1,
message: 0x1234,
where_v: 10,
where_h: 20,
modifiers: 0,
});
assert_eq!(context.event_queue().len(), 1);
assert_eq!(context.event_queue().front().unwrap().message, 0x1234);
}
#[test]
fn process_context_owns_canonical_menu_tracking() {
let mut context = ProcessContext::default();
assert!(context.menu_tracking().is_none());
let tracking = crate::menu_manager::test_process_menu_tracking(0x0012_3456);
context.set_menu_tracking(Some(tracking));
assert_eq!(
context.menu_tracking().map(|t| t.menu_handle),
Some(0x0012_3456)
);
if let Some(t) = context.menu_tracking_mut() {
t.highlighted_item = 3;
}
assert_eq!(
context
.menu_tracking()
.map(|t| (t.menu_handle, t.highlighted_item)),
Some((0x0012_3456, 3))
);
let taken = context.take_menu_tracking();
assert_eq!(taken.map(|t| t.menu_handle), Some(0x0012_3456));
assert!(context.menu_tracking().is_none());
let (queue, menu) = context.event_queue_and_menu_tracking_mut();
queue.push_back(QueuedEvent {
what: 2,
message: 0x5678,
where_v: 0,
where_h: 0,
modifiers: 0,
});
*menu = Some(crate::menu_manager::test_process_menu_tracking(0x0065_4321));
assert_eq!(context.event_queue().len(), 1);
assert_eq!(
context.menu_tracking().map(|t| t.menu_handle),
Some(0x0065_4321)
);
}
#[test]
fn adapters_transfer_pending_state_and_share_one_process_owner() {
let context = ProcessContext::default();
let mut classic_selection = SharedNativeMenuSelection::default();
assert!(classic_selection.stage((128, 2)));
let mut native_selection = SharedNativeMenuSelection::default();
context.attach_native_menu_selection(&mut classic_selection);
context.attach_native_menu_selection(&mut native_selection);
assert_eq!(native_selection.take(), Some((128, 2)));
assert!(classic_selection.is_none());
let mut classic_calls = SharedGuestCallStack::default();
classic_calls.begin_m68k(
GuestCallTarget {
isa: GuestIsa::M68k,
entry: 0x1000,
rtoc: 0,
},
0x2000,
0x3000,
);
let mut native_calls = SharedGuestCallStack::default();
context.attach_guest_calls(&mut classic_calls);
context.attach_guest_calls(&mut native_calls);
assert_eq!(native_calls.len(), 1);
assert!(native_calls.complete_m68k(0x2002, 0x3000));
assert!(classic_calls.is_empty());
}
#[test]
#[should_panic(expected = "cannot attach two active Menu Manager continuations")]
fn adopting_two_active_menu_continuations_is_always_rejected() {
let mut context = ProcessContext::default();
context.set_menu_tracking(Some(crate::menu_manager::test_process_menu_tracking(
0x1000,
)));
let mut second = Some(crate::menu_manager::test_process_menu_tracking(0x2000));
context.adopt_menu_tracking(&mut second);
}
#[test]
#[should_panic(expected = "cannot attach two pending native menu selections")]
fn attaching_two_pending_native_selections_is_always_rejected() {
let context = ProcessContext::default();
let mut first = SharedNativeMenuSelection::default();
let mut second = SharedNativeMenuSelection::default();
first.stage((128, 1));
second.stage((129, 2));
context.attach_native_menu_selection(&mut first);
context.attach_native_menu_selection(&mut second);
}
#[test]
#[should_panic(expected = "cannot attach two active guest-procedure continuation stacks")]
fn attaching_two_active_guest_call_stacks_is_always_rejected() {
fn begin_call(calls: &SharedGuestCallStack, entry: u32) {
calls.begin_m68k(
GuestCallTarget {
isa: GuestIsa::M68k,
entry,
rtoc: 0,
},
entry + 2,
0x3000,
);
}
let context = ProcessContext::default();
let mut first = SharedGuestCallStack::default();
let mut second = SharedGuestCallStack::default();
begin_call(&first, 0x1000);
begin_call(&second, 0x2000);
context.attach_guest_calls(&mut first);
context.attach_guest_calls(&mut second);
}
#[test]
fn process_context_owns_canonical_pending_memory_effects() {
let mut context = ProcessContext::default();
assert!(!context.has_pending_memory_effects());
assert!(context.pending_memory_effects().is_empty());
context.pending_memory_effects_mut().push(PendingHandleByteReplacement {
handle: 0x1000,
expected_ptr: 0x2000,
replacement: vec![1, 2, 3, 4],
});
assert!(context.has_pending_memory_effects());
assert_eq!(context.pending_memory_effects().len(), 1);
assert_eq!(context.pending_memory_effects()[0].handle, 0x1000);
let taken = context.take_pending_memory_effects();
assert_eq!(taken.len(), 1);
assert_eq!(taken[0].handle, 0x1000);
assert_eq!(taken[0].expected_ptr, 0x2000);
assert_eq!(taken[0].replacement, vec![1, 2, 3, 4]);
assert!(!context.has_pending_memory_effects());
let (queue, menu, effects) = context.event_queue_menu_tracking_and_memory_effects_mut();
queue.push_back(QueuedEvent {
what: 1,
message: 0x1234,
where_v: 0,
where_h: 0,
modifiers: 0,
});
*menu = Some(crate::menu_manager::test_process_menu_tracking(0x3000));
effects.push(PendingHandleByteReplacement {
handle: 0x4000,
expected_ptr: 0x5000,
replacement: vec![9, 8, 7],
});
assert_eq!(context.event_queue().len(), 1);
assert!(context.menu_tracking().is_some());
assert_eq!(context.pending_memory_effects().len(), 1);
}
}