Skip to content

Commit

Permalink
Added support for future on emulators
Browse files Browse the repository at this point in the history
This commit adds support for `future` when coding autosplitters for emulators, allowing to code similarly compared to native pc games.
The internal `update()` function gets called automatically in this case, so this also removes the need to manually call said function on every iteration of the main loop.
The downside is using a internal mutability for the main `state` and `ram_base` variable, however they are not publicly accessible so this should not raise many concerns.

```rust
async fn main() {
    loop {
        let emulator = ps1::Emulator::wait_attach().await;
        emulator.until_closes(async {
            loop {
                // TODO: Do something on every tick.
               next_tick().await;
            }
        }).await;
    }
}
```
  • Loading branch information
Jujstme committed Dec 25, 2023
1 parent a462bff commit 488eeab
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 142 deletions.
85 changes: 60 additions & 25 deletions src/emulator/gba/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
//! Support for attaching to Nintendo Gameboy Advance emulators.
use core::{
cell::Cell,
future::Future,
pin::Pin,
task::{Context, Poll},
};

use crate::{Address, Error, Process};
use bytemuck::CheckedBitPattern;

Expand All @@ -15,9 +22,9 @@ pub struct Emulator {
/// The attached emulator process
process: Process,
/// An enum stating which emulator is currently attached
state: State,
state: Cell<State>,
/// The memory address of the emulated RAM
ram_base: Option<[Address; 2]>, // [ewram, iwram]
ram_base: Cell<Option<[Address; 2]>>, // [ewram, iwram]
}

impl Emulator {
Expand All @@ -40,8 +47,8 @@ impl Emulator {

Some(Self {
process,
state,
ram_base: None,
state: Cell::new(state),
ram_base: Cell::new(None),
})
}

Expand All @@ -51,13 +58,24 @@ impl Emulator {
self.process.is_open()
}

/// Executes a future until the emulator process closes.
pub const fn until_closes<F>(&self, future: F) -> UntilEmulatorCloses<'_, F> {
UntilEmulatorCloses {
emulator: self,
future,
}
}

/// Calls the internal routines needed in order to find (and update, if
/// needed) the address of the emulated RAM.
///
/// Returns true if successful, false otherwise.
pub fn update(&mut self) -> bool {
if self.ram_base.is_none() {
self.ram_base = match match &mut self.state {
pub fn update(&self) -> bool {
let mut ram_base = self.ram_base.get();
let mut state = self.state.get();

if ram_base.is_none() {
ram_base = match match &mut state {
State::VisualBoyAdvance(x) => x.find_ram(&self.process),
State::Mgba(x) => x.find_ram(&self.process),
State::NoCashGba(x) => x.find_ram(&self.process),
Expand All @@ -70,21 +88,23 @@ impl Emulator {
};
}

let success = match &self.state {
State::VisualBoyAdvance(x) => x.keep_alive(&self.process, &mut self.ram_base),
State::Mgba(x) => x.keep_alive(&self.process, &self.ram_base),
State::NoCashGba(x) => x.keep_alive(&self.process, &mut self.ram_base),
let success = match &state {
State::VisualBoyAdvance(x) => x.keep_alive(&self.process, &mut ram_base),
State::Mgba(x) => x.keep_alive(&self.process, &ram_base),
State::NoCashGba(x) => x.keep_alive(&self.process, &mut ram_base),
State::Retroarch(x) => x.keep_alive(&self.process),
State::EmuHawk(x) => x.keep_alive(&self.process, &self.ram_base),
State::Mednafen(x) => x.keep_alive(&self.process, &mut self.ram_base),
State::EmuHawk(x) => x.keep_alive(&self.process, &ram_base),
State::Mednafen(x) => x.keep_alive(&self.process, &mut ram_base),
};

match success {
true => true,
false => {
self.ram_base = None;
false
}
self.state.set(state);

if success {
self.ram_base.set(ram_base);
true
} else {
self.ram_base.set(None);
false
}
}

Expand Down Expand Up @@ -120,9 +140,7 @@ impl Emulator {
return Err(Error {});
}

let Some([ewram, _]) = self.ram_base else {
return Err(Error {});
};
let [ewram, _] = self.ram_base.get().ok_or(Error {})?;
let end_offset = offset.checked_sub(0x02000000).unwrap_or(offset);

self.process.read(ewram + end_offset)
Expand All @@ -144,15 +162,32 @@ impl Emulator {
return Err(Error {});
}

let Some([_, iwram]) = self.ram_base else {
return Err(Error {});
};
let [_, iwram] = self.ram_base.get().ok_or(Error {})?;
let end_offset = offset.checked_sub(0x03000000).unwrap_or(offset);

self.process.read(iwram + end_offset)
}
}

/// A future that executes a future until the emulator closes.
pub struct UntilEmulatorCloses<'a, F> {
emulator: &'a Emulator,
future: F,
}

impl<F: Future<Output = ()>> Future for UntilEmulatorCloses<'_, F> {
type Output = ();

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if !self.emulator.is_open() {
return Poll::Ready(());
}
self.emulator.update();
// SAFETY: We are simply projecting the Pin.
unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) }
}
}

#[doc(hidden)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum State {
Expand Down
90 changes: 66 additions & 24 deletions src/emulator/gcn/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
//! Support for attaching to Nintendo Gamecube emulators.
use core::{
cell::Cell,
future::Future,
pin::Pin,
task::{Context, Poll},
};

use crate::{Address, Endian, Error, FromEndian, Process};
use bytemuck::CheckedBitPattern;

Expand All @@ -11,11 +18,11 @@ pub struct Emulator {
/// The attached emulator process
process: Process,
/// An enum stating which emulator is currently attached
state: State,
state: Cell<State>,
/// The memory address of the emulated RAM
mem1_base: Option<Address>,
mem1_base: Cell<Option<Address>>,
/// The endianness used by the emulator process
endian: Endian,
endian: Cell<Endian>,
}

impl Emulator {
Expand All @@ -33,9 +40,9 @@ impl Emulator {

Some(Self {
process,
state,
mem1_base: None,
endian: Endian::Big, // Endianness is usually Big across all GCN emulators
state: Cell::new(state),
mem1_base: Cell::new(None),
endian: Cell::new(Endian::Big), // Endianness is usually Big across all GCN emulators
})
}

Expand All @@ -45,32 +52,48 @@ impl Emulator {
self.process.is_open()
}

/// Executes a future until the emulator process closes.
pub const fn until_closes<F>(&self, future: F) -> UntilEmulatorCloses<'_, F> {
UntilEmulatorCloses {
emulator: self,
future,
}
}

/// Calls the internal routines needed in order to find (and update, if
/// needed) the address of the emulated RAM.
///
/// Returns true if successful, false otherwise.
pub fn update(&mut self) -> bool {
if self.mem1_base.is_none() {
let mem1_base = match &mut self.state {
State::Dolphin(x) => x.find_ram(&self.process, &mut self.endian),
State::Retroarch(x) => x.find_ram(&self.process, &mut self.endian),
pub fn update(&self) -> bool {
let mut mem1_base = self.mem1_base.get();
let mut state = self.state.get();
let mut endian = self.endian.get();

if mem1_base.is_none() {
mem1_base = match match &mut state {
State::Dolphin(x) => x.find_ram(&self.process, &mut endian),
State::Retroarch(x) => x.find_ram(&self.process, &mut endian),
} {
None => return false,
something => something,
};
if mem1_base.is_none() {
return false;
}
self.mem1_base = mem1_base;
}

let success = match &self.state {
State::Dolphin(x) => x.keep_alive(&self.process, &self.mem1_base),
State::Retroarch(x) => x.keep_alive(&self.process, &self.mem1_base),
let success = match &state {
State::Dolphin(x) => x.keep_alive(&self.process, &mem1_base),
State::Retroarch(x) => x.keep_alive(&self.process, &mem1_base),
};

if !success {
self.mem1_base = None;
}
self.state.set(state);
self.endian.set(endian);

success
if success {
self.mem1_base.set(mem1_base);
true
} else {
self.mem1_base.set(None);
false
}
}

/// Reads raw data from the emulated RAM ignoring all endianness settings.
Expand All @@ -92,7 +115,7 @@ impl Emulator {
return Err(Error {});
}

let mem1 = self.mem1_base.ok_or(Error {})?;
let mem1 = self.mem1_base.get().ok_or(Error {})?;
let end_offset = offset.checked_sub(0x80000000).unwrap_or(offset);

self.process.read(mem1 + end_offset)
Expand All @@ -112,7 +135,26 @@ impl Emulator {
pub fn read<T: CheckedBitPattern + FromEndian>(&self, offset: u32) -> Result<T, Error> {
Ok(self
.read_ignoring_endianness::<T>(offset)?
.from_endian(self.endian))
.from_endian(self.endian.get()))
}
}

/// A future that executes a future until the emulator closes.
pub struct UntilEmulatorCloses<'a, F> {
emulator: &'a Emulator,
future: F,
}

impl<F: Future<Output = ()>> Future for UntilEmulatorCloses<'_, F> {
type Output = ();

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if !self.emulator.is_open() {
return Poll::Ready(());
}
self.emulator.update();
// SAFETY: We are simply projecting the Pin.
unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) }
}
}

Expand Down
Loading

0 comments on commit 488eeab

Please sign in to comment.