Added joystick host adapter

This commit is contained in:
transistor 2021-10-31 11:00:14 -07:00
parent a02d8e5241
commit 8fe76334af
9 changed files with 173 additions and 74 deletions

View File

@ -6,7 +6,7 @@ use minifb::{self, Key};
use moa::error::Error;
use moa::system::System;
use moa::host::traits::{Host, WindowUpdater};
use moa::host::traits::{Host, JoystickDevice, JoystickUpdater, WindowUpdater};
const WIDTH: usize = 640;
@ -15,6 +15,7 @@ const HEIGHT: usize = 360;
pub struct MiniFrontend {
pub buffer: Mutex<Vec<u32>>,
pub updater: Mutex<Option<Box<dyn WindowUpdater>>>,
pub input: Mutex<Option<Box<dyn JoystickUpdater>>>,
}
impl Host for MiniFrontend {
@ -26,6 +27,19 @@ impl Host for MiniFrontend {
*unlocked = Some(updater);
Ok(())
}
fn register_joystick(&self, device: JoystickDevice, input: Box<dyn JoystickUpdater>) -> Result<(), Error> {
if device != JoystickDevice::A {
return Ok(())
}
let mut unlocked = self.input.lock().unwrap();
if unlocked.is_some() {
return Err(Error::new("A window updater has already been registered with the frontend"));
}
*unlocked = Some(input);
Ok(())
}
}
impl MiniFrontend {
@ -33,6 +47,7 @@ impl MiniFrontend {
MiniFrontend {
buffer: Mutex::new(vec![0; WIDTH * HEIGHT]),
updater: Mutex::new(None),
input: Mutex::new(None),
}
}
@ -54,13 +69,24 @@ impl MiniFrontend {
while window.is_open() && !window.is_key_down(Key::Escape) {
system.run_for(16_600_000).unwrap();
match &mut *self.updater.lock().unwrap() {
Some(updater) => {
let mut buffer = self.buffer.lock().unwrap();
updater.update_frame(WIDTH as u32, HEIGHT as u32, &mut buffer);
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
},
None => { }
if let Some(keys) = window.get_keys_pressed(minifb::KeyRepeat::Yes) {
let mut modifiers: u16 = 0;
for key in keys {
match key {
Key::Enter => { modifiers |= 0xffff; },
Key::D => { system.get_interrupt_controller().target.as_ref().map(|target| target.borrow_mut().as_debuggable().unwrap().enable_debugging()); },
_ => { },
}
}
if let Some(updater) = &mut *self.input.lock().unwrap() {
updater.update_joystick(modifiers);
}
}
if let Some(updater) = &mut *self.updater.lock().unwrap() {
let mut buffer = self.buffer.lock().unwrap();
updater.update_frame(WIDTH as u32, HEIGHT as u32, &mut buffer);
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
}
}
}

View File

@ -3,10 +3,18 @@ use std::sync::{Arc, Mutex};
use crate::error::Error;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum JoystickDevice {
A,
B,
C,
D,
}
pub trait Host {
//fn create_pty(&self) -> Result<Box<dyn Tty>, Error>;
fn add_window(&self, updater: Box<dyn WindowUpdater>) -> Result<(), Error>;
fn register_joystick(&self, device: JoystickDevice, input: Box<dyn JoystickUpdater>) -> Result<(), Error> { Err(Error::new("Not supported")) }
}
pub trait Tty {
@ -19,8 +27,13 @@ pub trait WindowUpdater: Send {
fn update_frame(&mut self, width: u32, height: u32, bitmap: &mut [u32]);
}
pub trait JoystickUpdater: Send {
fn update_joystick(&mut self, modifiers: u16);
}
pub trait BlitableSurface {
fn set_size(&mut self, width: u32, height: u32);
fn blit<B: Iterator<Item=u32>>(&mut self, pos_x: u32, pos_y: u32, bitmap: B, width: u32, height: u32);
}

View File

@ -5,34 +5,6 @@ use crate::error::Error;
use crate::devices::TransmutableBox;
pub struct Signal {
pub current: bool,
pub previous: bool,
}
impl Signal {
pub fn new() -> Signal {
Signal {
current: false,
previous: false,
}
}
pub fn has_changed(&mut self) -> Option<bool> {
if self.current != self.previous {
self.previous = self.current;
Some(self.current)
} else {
None
}
}
pub fn set(&mut self, value: bool) {
self.current = value;
}
}
pub struct InterruptController {
pub target: Option<TransmutableBox>,
pub interrupts: Vec<(bool, u8)>,

View File

@ -3,6 +3,7 @@
pub mod error;
pub mod memory;
pub mod timers;
pub mod signals;
pub mod devices;
pub mod interrupts;
pub mod system;

View File

@ -13,12 +13,14 @@ use crate::host::traits::{Host, WindowUpdater};
pub fn build_genesis<H: Host>(host: &H) -> Result<System, Error> {
let mut system = System::new();
//let rom = MemoryBlock::load("binaries/genesis/Sonic The Hedgehog (W) (REV 01) [!].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/F1 World Championship (JUE) [!].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Out of this World (U) [!].bin").unwrap();
let rom = MemoryBlock::load("binaries/genesis/Earthworm Jim (U) [h1].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Sonic The Hedgehog (W) (REV 00) [!].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Sonic The Hedgehog (W) (REV 01) [!].bin").unwrap();
let rom = MemoryBlock::load("binaries/genesis/Earthworm Jim (U) [h1].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Home Alone (beta).bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/F1 World Championship (JUE) [!].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Ren and Stimpy's Invention (U) [!].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Out of this World (U) [!].bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Ghostbusters (REV 00) (JUE).bin").unwrap();
//let rom = MemoryBlock::load("binaries/genesis/Teenage Mutant Ninja Turtles - The Hyperstone Heist (U) [!].bin").unwrap();
system.add_addressable_device(0x00000000, wrap_transmutable(rom)).unwrap();
@ -31,13 +33,14 @@ pub fn build_genesis<H: Host>(host: &H) -> Result<System, Error> {
system.add_addressable_device(0x00A00000, wrap_transmutable(coproc_shared_mem)).unwrap();
let controllers = genesis::controllers::GenesisControllerDevice::new(host);
let controllers = genesis::controllers::GenesisController::create(host)?;
let interrupt = controllers.get_interrupt_signal();
system.add_addressable_device(0x00a10000, wrap_transmutable(controllers)).unwrap();
let coproc = genesis::coproc_memory::CoprocessorMemory::new();
system.add_addressable_device(0x00a11000, wrap_transmutable(coproc)).unwrap();
let vdp = genesis::ym7101::Ym7101::new(host);
let vdp = genesis::ym7101::Ym7101::new(host, interrupt);
system.add_addressable_device(0x00c00000, wrap_transmutable(vdp)).unwrap();

View File

@ -1,6 +1,10 @@
use std::sync::{Arc, Mutex};
use crate::error::Error;
use crate::signals::{Signal, SyncSignal};
use crate::devices::{Address, Addressable, Transmutable, MAX_READ};
use crate::host::traits::{Host, JoystickDevice, JoystickUpdater};
const REG_VERSION: Address = 0x01;
@ -21,7 +25,7 @@ pub struct GenesisControllerPort {
/// Data contains bits:
/// 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
/// X | Y | Z | MODE | START | A | B | C | RIGHT | LEFT | DOWN | UP
pub data: u16,
pub data: SyncSignal<u16>,
pub ctrl: u8,
pub th_count: u8,
@ -33,7 +37,7 @@ pub struct GenesisControllerPort {
impl GenesisControllerPort {
pub fn new() -> Self {
Self {
data: 0,
data: SyncSignal::new(0),
ctrl: 0,
th_count: 0,
next_read: 0,
@ -48,17 +52,18 @@ impl GenesisControllerPort {
if (self.next_read ^ prev_th) != 0 {
// TH bit was toggled
self.th_count += 1;
let data = self.data.get();
self.next_read = match self.th_count {
0 => self.next_read | ((self.data & 0x003F) as u8),
1 => self.next_read | (((self.data & 0x00C0) >> 2) as u8) | ((self.data & 0x0003) as u8),
2 => self.next_read | ((self.data & 0x003F) as u8),
3 => self.next_read | (((self.data & 0x00C0) >> 2) as u8),
4 => self.next_read | ((self.data & 0x0030) as u8) | (((self.data & 0x0F00) >> 8) as u8),
5 => self.next_read | (((self.data & 0x00C0) >> 2) as u8) | 0x0F,
6 => self.next_read | ((self.data & 0x003F) as u8),
0 => self.next_read | ((data & 0x003F) as u8),
1 => self.next_read | (((data & 0x00C0) >> 2) as u8) | ((data & 0x0003) as u8),
2 => self.next_read | ((data & 0x003F) as u8),
3 => self.next_read | (((data & 0x00C0) >> 2) as u8),
4 => self.next_read | ((data & 0x0030) as u8) | (((data & 0x0F00) >> 8) as u8),
5 => self.next_read | (((data & 0x00C0) >> 2) as u8) | 0x0F,
6 => self.next_read | ((data & 0x003F) as u8),
7 => {
self.th_count = 0;
self.next_read | (((self.data & 0x00C0) >> 2) as u8) | ((self.data & 0x0003) as u8)
self.next_read | (((data & 0x00C0) >> 2) as u8) | ((data & 0x0003) as u8)
},
_ => {
self.th_count = 0;
@ -69,12 +74,24 @@ impl GenesisControllerPort {
}
}
pub struct GenesisControllerUpdater(SyncSignal<u16>, SyncSignal<bool>);
impl JoystickUpdater for GenesisControllerUpdater {
fn update_joystick(&mut self, modifiers: u16) {
self.0.set(modifiers);
if modifiers != 0 {
self.1.set(true);
}
}
}
pub struct GenesisController {
pub port_1: GenesisControllerPort,
pub port_2: GenesisControllerPort,
pub expansion: GenesisControllerPort,
pub interrupt: SyncSignal<bool>,
}
impl GenesisController {
@ -83,8 +100,24 @@ impl GenesisController {
port_1: GenesisControllerPort::new(),
port_2: GenesisControllerPort::new(),
expansion: GenesisControllerPort::new(),
interrupt: SyncSignal::new(false),
}
}
pub fn create<H: Host>(host: &H) -> Result<Self, Error> {
let controller = GenesisController::new();
let joystick1 = Box::new(GenesisControllerUpdater(controller.port_1.data.clone(), controller.interrupt.clone()));
host.register_joystick(JoystickDevice::A, joystick1)?;
let joystick2 = Box::new(GenesisControllerUpdater(controller.port_2.data.clone(), controller.interrupt.clone()));
host.register_joystick(JoystickDevice::B, joystick2)?;
Ok(controller)
}
pub fn get_interrupt_signal(&self) -> SyncSignal<bool> {
self.interrupt.clone()
}
}
impl Addressable for GenesisController {

View File

@ -4,6 +4,7 @@ use std::sync::{Arc, Mutex};
use crate::error::Error;
use crate::system::System;
use crate::signals::SyncSignal;
use crate::devices::{Clock, ClockElapsed, Address, Addressable, Steppable, Transmutable, MAX_READ, read_beu16, read_beu32, write_beu16};
use crate::host::traits::{Host, BlitableSurface};
use crate::host::gfx::{Frame, FrameSwapper};
@ -91,8 +92,6 @@ pub struct Ym7101State {
pub h_clock: u32,
pub v_clock: u32,
pub h_scanlines: u8,
pub reset_int: bool,
}
impl Ym7101State {
@ -114,8 +113,6 @@ impl Ym7101State {
h_clock: 0,
v_clock: 0,
h_scanlines: 0,
reset_int: false,
}
}
@ -290,18 +287,19 @@ impl Ym7101State {
let (scroll_h, scroll_v) = self.get_scroll_size();
for i in 0..(scroll_h as u32 * 2) {
let sprite_addr = (sprite_table + (i * 8)) as usize;
let v_pos = read_beu16(&self.vram[sprite_addr..]);
let size = self.vram[sprite_addr + 2];
let sprite_data = &self.vram[((sprite_table + (i * 8)) as usize)..];
let v_pos = read_beu16(&sprite_data[0..]);
let size = sprite_data[2];
let link = sprite_data[3];
let pattern_name = read_beu16(&sprite_data[4..]);
let h_pos = read_beu16(&sprite_data[6..]);
let (size_h, size_v) = (((size >> 2) & 0x03) as u16, (size & 0x03) as u16);
let link = self.vram[sprite_addr + 3];
let pattern_name = read_beu16(&self.vram[(sprite_addr + 4)..]);
let h_pos = read_beu16(&self.vram[(sprite_addr + 6)..]);
for h in 0..size_h {
for v in 0..size_v {
let iter = self.get_pattern_iter(pattern_name + (h * size_v) + v);
frame.blit((h_pos + h * 8) as u32, (v_pos + v * 8) as u32, iter, 8, 8);
frame.blit((h_pos + (h * 8)) as u32, (v_pos + (v * 8)) as u32, iter, 8, 8);
}
}
}
@ -374,10 +372,11 @@ impl<'a> Iterator for PatternIterator<'a> {
pub struct Ym7101 {
pub swapper: Arc<Mutex<FrameSwapper>>,
pub state: Ym7101State,
pub external_interrupt: SyncSignal<bool>,
}
impl Ym7101 {
pub fn new<H: Host>(host: &H) -> Ym7101 {
pub fn new<H: Host>(host: &H, external_interrupt: SyncSignal<bool>) -> Ym7101 {
let swapper = FrameSwapper::new_shared();
swapper.lock().map(|mut swapper| {
swapper.current.set_size(320, 224);
@ -389,6 +388,7 @@ impl Ym7101 {
Ym7101 {
swapper,
state: Ym7101State::new(),
external_interrupt,
}
}
}
@ -408,9 +408,9 @@ impl Steppable for Ym7101 {
let diff = (system.clock - self.state.last_clock) as u32;
self.state.last_clock = system.clock;
if self.state.reset_int {
system.get_interrupt_controller().set(false, 4, 28)?;
system.get_interrupt_controller().set(false, 6, 30)?;
if self.external_interrupt.get() {
self.external_interrupt.set(false);
system.get_interrupt_controller().set(true, 2, 26)?;
}
self.state.h_clock += diff;
@ -424,7 +424,6 @@ impl Steppable for Ym7101 {
if self.state.hsync_int_enabled() && self.state.h_scanlines == 0 {
self.state.h_scanlines = self.state.regs[REG_H_INTERRUPT];
system.get_interrupt_controller().set(true, 4, 28)?;
self.state.reset_int = true;
}
}
@ -437,7 +436,6 @@ impl Steppable for Ym7101 {
self.state.v_clock = 0;
if self.state.vsync_int_enabled() {
system.get_interrupt_controller().set(true, 6, 30)?;
self.state.reset_int = true;
}
let mut swapper = self.swapper.lock().unwrap();

41
src/signals.rs Normal file
View File

@ -0,0 +1,41 @@
use std::rc::Rc;
use std::cell::Cell;
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug)]
pub struct Signal<T: Copy>(Rc<Cell<T>>);
impl<T: Copy> Signal<T> {
pub fn new(init: T) -> Signal<T> {
Signal(Rc::new(Cell::new(init)))
}
pub fn set(&mut self, value: T) {
self.0.set(value);
}
pub fn get(&mut self) -> T {
self.0.get()
}
}
#[derive(Clone, Debug)]
pub struct SyncSignal<T: Copy>(Arc<Mutex<T>>);
impl<T: Copy> SyncSignal<T> {
pub fn new(init: T) -> SyncSignal<T> {
SyncSignal(Arc::new(Mutex::new(init)))
}
pub fn set(&mut self, value: T) {
*(self.0.lock().unwrap()) = value;
}
pub fn get(&mut self) -> T {
*(self.0.lock().unwrap())
}
}

View File

@ -1,15 +1,29 @@
* check out game Out Of this World
* how can you do devices that change their dadress map duritg operation, like mac which puts rom at 0 nad ram at 600000 temperarily
* fix ym7101 to better handle V/H interrupts (right now it sets and then the next step will clear, but it'd be nice if it could 'edge trigger')
* how do you do the external interrupt, which is controlled by the YM7101 but the ... input comes from the controllers. I suppose it could actually come through the updater,
the video would have a reference to the controllers even... I don't really like config details in the sub devices, so is there a way to use JoystickUpdater or something like
it, such that the YM7101 would be given two generic joystick objects, which are also passed to or produced by the controller impl, so you could in theory swap the controllers out
and the YM7101 should use them just the same
* sort out how inputing keys will work
* separate the debugger out of m68k, use on_debug or something to print out debug info
* make devices nameable, using a hashmap to store them
* how can you do devices that change their address map during operation, like mac which puts rom at 0 and ram at 600000 temporarily
* i need a better way of handling disperate reads/writes to I/O spaces, rather than having multiple devices or having a massive chunk of address space allocated, continuously
* should you modify Addressable to also take the absolute address as input? I'm thinking of how the same device could be mapped to multiple addresses in memory instead
of taking up a whole range of addresses
* could you use a generic sharable signal thing for sharing data, such as the VIA in mac128 where a single output bit determines the video mode (which would be a separate device)
So both could share the same Signal, one setting it and the other reading it, but how would you actually configure/build that?
We Need:
* devices that change address mapping during operation
* device that can be mapped to multiple separated locations while sharing data
* subdevices that can share data (eg. signal from one to another (mac128 via outputs), or being a subdevice with a special trait (joystick))
* implement a Z80
* maybe see about a Mac 128k or something
@ -18,8 +32,6 @@
* make the ym7101 set/reset the v_int occurred flag based on the interrupt controller
* separate the debugger out of m68k
* make devices nameable, using a hashmap to store them
* you could modify read()/write() in Addressable to return the number of bytes read or written for dynamic bus sizing used by the MC68020+
* should you simulate bus arbitration?