mirror of
https://github.com/transistorfet/moa.git
synced 2024-12-21 05:30:32 +00:00
Replaced controller/key/mouse updaters with event queues
This commit is contained in:
parent
3471eb4e8c
commit
112bd8219b
@ -8,7 +8,7 @@ pub enum ControllerDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ControllerEvent {
|
pub enum ControllerInput {
|
||||||
DpadUp(bool),
|
DpadUp(bool),
|
||||||
DpadDown(bool),
|
DpadDown(bool),
|
||||||
DpadLeft(bool),
|
DpadLeft(bool),
|
||||||
@ -23,3 +23,18 @@ pub enum ControllerEvent {
|
|||||||
Mode(bool),
|
Mode(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ControllerEvent {
|
||||||
|
pub device: ControllerDevice,
|
||||||
|
pub input: ControllerInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControllerEvent {
|
||||||
|
pub fn new(device: ControllerDevice, input: ControllerInput) -> Self {
|
||||||
|
Self {
|
||||||
|
device,
|
||||||
|
input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::Error;
|
|
||||||
use crate::ClockTime;
|
use crate::ClockTime;
|
||||||
use crate::host::traits::ClockedQueue;
|
use crate::host::traits::ClockedQueue;
|
||||||
|
|
||||||
@ -113,9 +112,8 @@ impl Frame {
|
|||||||
|
|
||||||
pub fn frame_queue(width: u32, height: u32) -> (FrameSender, FrameReceiver) {
|
pub fn frame_queue(width: u32, height: u32) -> (FrameSender, FrameReceiver) {
|
||||||
let sender = FrameSender {
|
let sender = FrameSender {
|
||||||
max_size: (width, height),
|
|
||||||
encoding: Arc::new(Mutex::new(PixelEncoding::RGBA)),
|
encoding: Arc::new(Mutex::new(PixelEncoding::RGBA)),
|
||||||
queue: Default::default(),
|
queue: ClockedQueue::new(10),
|
||||||
};
|
};
|
||||||
|
|
||||||
let receiver = FrameReceiver {
|
let receiver = FrameReceiver {
|
||||||
@ -128,20 +126,11 @@ pub fn frame_queue(width: u32, height: u32) -> (FrameSender, FrameReceiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FrameSender {
|
pub struct FrameSender {
|
||||||
max_size: (u32, u32),
|
|
||||||
encoding: Arc<Mutex<PixelEncoding>>,
|
encoding: Arc<Mutex<PixelEncoding>>,
|
||||||
queue: ClockedQueue<Frame>,
|
queue: ClockedQueue<Frame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameSender {
|
impl FrameSender {
|
||||||
pub fn new(width: u32, height: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
max_size: (width, height),
|
|
||||||
encoding: Arc::new(Mutex::new(PixelEncoding::RGBA)),
|
|
||||||
queue: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encoding(&self) -> PixelEncoding {
|
pub fn encoding(&self) -> PixelEncoding {
|
||||||
*self.encoding.lock().unwrap()
|
*self.encoding.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,7 @@ pub enum Key {
|
|||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct KeyEvent {
|
pub struct KeyEvent {
|
||||||
pub key: Key,
|
pub key: Key,
|
||||||
pub state: bool,
|
pub state: bool,
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
mod traits;
|
mod traits;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod gfx;
|
mod gfx;
|
||||||
|
mod input;
|
||||||
mod controllers;
|
mod controllers;
|
||||||
mod mouse;
|
mod mouse;
|
||||||
|
|
||||||
pub use self::gfx::{Pixel, PixelEncoding, Frame, FrameSender, FrameReceiver, frame_queue};
|
pub use self::gfx::{Pixel, PixelEncoding, Frame, FrameSender, FrameReceiver, frame_queue};
|
||||||
pub use self::keys::{Key, KeyEvent};
|
pub use self::keys::{Key, KeyEvent};
|
||||||
pub use self::mouse::{MouseButton, MouseEventType, MouseEvent, MouseState};
|
pub use self::mouse::{MouseButton, MouseEventType, MouseEvent, MouseState};
|
||||||
pub use self::controllers::{ControllerDevice, ControllerEvent};
|
pub use self::controllers::{ControllerDevice, ControllerInput, ControllerEvent};
|
||||||
pub use self::traits::{Host, Tty, ControllerUpdater, KeyboardUpdater, MouseUpdater, Audio, HostData, ClockedQueue, DummyAudio};
|
pub use self::input::{EventSender, EventReceiver, event_queue};
|
||||||
|
pub use self::traits::{Host, Tty, Audio, HostData, ClockedQueue, DummyAudio};
|
||||||
|
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MouseButton {
|
pub enum MouseButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Middle,
|
Middle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MouseEventType {
|
pub enum MouseEventType {
|
||||||
Down(MouseButton),
|
Down(MouseButton),
|
||||||
Up(MouseButton),
|
Up(MouseButton),
|
||||||
Move,
|
Move,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct MouseEvent {
|
pub struct MouseEvent {
|
||||||
pub etype: MouseEventType,
|
pub etype: MouseEventType,
|
||||||
pub pos: (u32, u32),
|
pub pos: (u32, u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, PartialEq, Eq)]
|
#[derive(Copy, Clone, Default, PartialEq, Eq)]
|
||||||
pub struct MouseState {
|
pub struct MouseState {
|
||||||
pub buttons: [bool; 3],
|
pub buttons: [bool; 3],
|
||||||
pub pos: (u32, u32),
|
pub pos: (u32, u32),
|
||||||
|
@ -3,10 +3,11 @@ use std::collections::VecDeque;
|
|||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use crate::{ClockTime, Error};
|
use crate::{ClockTime, Error};
|
||||||
use crate::host::gfx::{PixelEncoding, Pixel, Frame, FrameReceiver};
|
use crate::host::gfx::FrameReceiver;
|
||||||
use crate::host::keys::KeyEvent;
|
use crate::host::keys::KeyEvent;
|
||||||
use crate::host::controllers::{ControllerDevice, ControllerEvent};
|
use crate::host::controllers::ControllerEvent;
|
||||||
use crate::host::mouse::MouseEvent;
|
use crate::host::mouse::MouseEvent;
|
||||||
|
use crate::host::input::EventSender;
|
||||||
|
|
||||||
pub trait Host {
|
pub trait Host {
|
||||||
fn add_pty(&self) -> Result<Box<dyn Tty>, Error> {
|
fn add_pty(&self) -> Result<Box<dyn Tty>, Error> {
|
||||||
@ -21,15 +22,15 @@ pub trait Host {
|
|||||||
Err(Error::new("This frontend doesn't support the sound"))
|
Err(Error::new("This frontend doesn't support the sound"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_controller(&mut self, _device: ControllerDevice, _input: Box<dyn ControllerUpdater>) -> Result<(), Error> {
|
fn register_controllers(&mut self, _sender: EventSender<ControllerEvent>) -> Result<(), Error> {
|
||||||
Err(Error::new("This frontend doesn't support game controllers"))
|
Err(Error::new("This frontend doesn't support game controllers"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_keyboard(&mut self, _input: Box<dyn KeyboardUpdater>) -> Result<(), Error> {
|
fn register_keyboard(&mut self, _sender: EventSender<KeyEvent>) -> Result<(), Error> {
|
||||||
Err(Error::new("This frontend doesn't support the keyboard"))
|
Err(Error::new("This frontend doesn't support the keyboard"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_mouse(&mut self, _input: Box<dyn MouseUpdater>) -> Result<(), Error> {
|
fn register_mouse(&mut self, _sender: EventSender<MouseEvent>) -> Result<(), Error> {
|
||||||
Err(Error::new("This frontend doesn't support the mouse"))
|
Err(Error::new("This frontend doesn't support the mouse"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,18 +42,6 @@ pub trait Tty {
|
|||||||
fn write(&mut self, output: u8) -> bool;
|
fn write(&mut self, output: u8) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ControllerUpdater: Send {
|
|
||||||
fn update_controller(&self, event: ControllerEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait KeyboardUpdater: Send {
|
|
||||||
fn update_keyboard(&self, event: KeyEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MouseUpdater: Send {
|
|
||||||
fn update_mouse(&self, event: MouseEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Audio {
|
pub trait Audio {
|
||||||
fn samples_per_second(&self) -> usize;
|
fn samples_per_second(&self) -> usize;
|
||||||
fn space_available(&self) -> usize;
|
fn space_available(&self) -> usize;
|
||||||
@ -84,12 +73,22 @@ impl<T: Copy> HostData<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct ClockedQueue<T>(Arc<Mutex<VecDeque<(ClockTime, T)>>>);
|
pub struct ClockedQueue<T>(Arc<Mutex<VecDeque<(ClockTime, T)>>>, usize);
|
||||||
|
|
||||||
impl<T: Clone> ClockedQueue<T> {
|
impl<T: Clone> ClockedQueue<T> {
|
||||||
|
pub fn new(max: usize) -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(VecDeque::new())), max)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&self, clock: ClockTime, data: T) {
|
pub fn push(&self, clock: ClockTime, data: T) {
|
||||||
self.0.lock().unwrap().push_back((clock, data));
|
let mut queue = self.0.lock().unwrap();
|
||||||
|
if queue.len() > self.1 {
|
||||||
|
//log::warn!("dropping data from queue due to limit of {} items", self.1);
|
||||||
|
queue.pop_front();
|
||||||
|
}
|
||||||
|
queue.push_back((clock, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_next(&self) -> Option<(ClockTime, T)> {
|
pub fn pop_next(&self) -> Option<(ClockTime, T)> {
|
||||||
@ -100,7 +99,7 @@ impl<T: Clone> ClockedQueue<T> {
|
|||||||
self.0.lock().unwrap().drain(..).last()
|
self.0.lock().unwrap().drain(..).last()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unpop(&mut self, clock: ClockTime, data: T) {
|
pub fn unpop(&self, clock: ClockTime, data: T) {
|
||||||
self.0.lock().unwrap().push_front((clock, data));
|
self.0.lock().unwrap().push_front((clock, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,11 @@ use moa_core::host::{Audio, ClockedQueue};
|
|||||||
|
|
||||||
pub const SAMPLE_RATE: usize = 48000;
|
pub const SAMPLE_RATE: usize = 48000;
|
||||||
|
|
||||||
|
pub struct Sample(f32, f32);
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct AudioFrame {
|
pub struct AudioFrame {
|
||||||
|
//pub sample_rate: usize,
|
||||||
pub data: Vec<(f32, f32)>,
|
pub data: Vec<(f32, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +26,7 @@ pub struct AudioSource {
|
|||||||
|
|
||||||
impl AudioSource {
|
impl AudioSource {
|
||||||
pub fn new(mixer: Arc<Mutex<AudioMixer>>) -> Self {
|
pub fn new(mixer: Arc<Mutex<AudioMixer>>) -> Self {
|
||||||
let queue = ClockedQueue::default();
|
let queue = ClockedQueue::new(5000);
|
||||||
let (id, sample_rate, frame_size) = {
|
let (id, sample_rate, frame_size) = {
|
||||||
let mut mixer = mixer.lock().unwrap();
|
let mut mixer = mixer.lock().unwrap();
|
||||||
let id = mixer.add_source(queue.clone());
|
let id = mixer.add_source(queue.clone());
|
||||||
@ -124,6 +127,10 @@ impl AudioMixer {
|
|||||||
self.sources.len() - 1
|
self.sources.len() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn num_sources(&self) -> usize {
|
||||||
|
self.sources.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_sink(&mut self) -> Arc<Mutex<AudioOutput>> {
|
pub fn get_sink(&mut self) -> Arc<Mutex<AudioOutput>> {
|
||||||
self.output.clone()
|
self.output.clone()
|
||||||
}
|
}
|
||||||
@ -214,11 +221,9 @@ impl AudioMixer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct AudioOutput {
|
pub struct AudioOutput {
|
||||||
frame_size: usize,
|
frame_size: usize,
|
||||||
sequence_num: usize,
|
sequence_num: usize,
|
||||||
last_frame: Option<AudioFrame>,
|
|
||||||
output: VecDeque<AudioFrame>,
|
output: VecDeque<AudioFrame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +232,6 @@ impl AudioOutput {
|
|||||||
Arc::new(Mutex::new(Self {
|
Arc::new(Mutex::new(Self {
|
||||||
frame_size: 0,
|
frame_size: 0,
|
||||||
sequence_num: 0,
|
sequence_num: 0,
|
||||||
last_frame: None,
|
|
||||||
output: VecDeque::with_capacity(2),
|
output: VecDeque::with_capacity(2),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use cpal::{Stream, SampleRate, SampleFormat, StreamConfig, traits::{DeviceTrait, HostTrait, StreamTrait}};
|
use cpal::{Stream, SampleRate, SampleFormat, StreamConfig, StreamInstant, OutputCallbackInfo, traits::{DeviceTrait, HostTrait, StreamTrait}};
|
||||||
|
|
||||||
use moa_core::{warn, error};
|
use moa_core::{warn, error};
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ impl CpalAudioOutput {
|
|||||||
.with_sample_rate(SampleRate(SAMPLE_RATE as u32))
|
.with_sample_rate(SampleRate(SAMPLE_RATE as u32))
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let data_callback = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
let data_callback = move |data: &mut [f32], info: &OutputCallbackInfo| {
|
||||||
let result = if let Ok(mut output) = output.lock() {
|
let result = if let Ok(mut output) = output.lock() {
|
||||||
output.set_frame_size(data.len() / 2);
|
output.set_frame_size(data.len() / 2);
|
||||||
output.pop_next()
|
output.pop_next()
|
||||||
@ -59,5 +59,13 @@ impl CpalAudioOutput {
|
|||||||
stream,
|
stream,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_mute(&self, mute: bool) {
|
||||||
|
if mute {
|
||||||
|
self.stream.pause().unwrap();
|
||||||
|
} else {
|
||||||
|
self.stream.play().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +1,29 @@
|
|||||||
|
|
||||||
use std::sync::mpsc;
|
|
||||||
|
|
||||||
use moa_peripherals_yamaha::{Ym2612, Sn76489};
|
use moa_peripherals_yamaha::{Ym2612, Sn76489};
|
||||||
|
|
||||||
use moa_core::host::{Host, Frame, FrameQueue, PixelEncoding, WindowUpdater, KeyboardUpdater, Key, KeyEvent /*, MouseUpdater, MouseState, MouseEvent*/};
|
use moa_core::host::{self, Host, Frame, FrameSender, PixelEncoding, Key, KeyEvent, EventReceiver};
|
||||||
use moa_core::{System, Error, ClockTime, ClockDuration, Frequency, Address, Addressable, Steppable, Transmutable, TransmutableBox, wrap_transmutable};
|
use moa_core::{System, Error, ClockTime, ClockDuration, Frequency, Address, Addressable, Steppable, Transmutable, TransmutableBox, wrap_transmutable};
|
||||||
|
|
||||||
|
const SCREEN_WIDTH: u32 = 384;
|
||||||
pub struct SynthControlsUpdater(mpsc::Sender<KeyEvent>);
|
const SCREEN_HEIGHT: u32 = 128;
|
||||||
|
|
||||||
impl KeyboardUpdater for SynthControlsUpdater {
|
|
||||||
fn update_keyboard(&mut self, event: KeyEvent) {
|
|
||||||
self.0.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl MouseUpdater for SynthControlsUpdater {
|
|
||||||
// fn update_mouse(&mut self, event: MouseEvent) {
|
|
||||||
// self.0.send(event).unwrap();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
struct SynthControl {
|
struct SynthControl {
|
||||||
queue: FrameQueue,
|
key_receiver: EventReceiver<KeyEvent>,
|
||||||
receiver: mpsc::Receiver<KeyEvent>,
|
frame_sender: FrameSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SynthControl {
|
impl SynthControl {
|
||||||
pub fn new(queue: FrameQueue, receiver: mpsc::Receiver<KeyEvent>) -> Self {
|
pub fn new(key_receiver: EventReceiver<KeyEvent>, frame_sender: FrameSender) -> Self {
|
||||||
Self {
|
Self {
|
||||||
queue,
|
key_receiver,
|
||||||
receiver,
|
frame_sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Steppable for SynthControl {
|
impl Steppable for SynthControl {
|
||||||
fn step(&mut self, system: &System) -> Result<ClockDuration, Error> {
|
fn step(&mut self, system: &System) -> Result<ClockDuration, Error> {
|
||||||
if let Ok(event) = self.receiver.try_recv() {
|
if let Some(event) = self.key_receiver.receive() {
|
||||||
|
|
||||||
match event.key {
|
match event.key {
|
||||||
Key::Enter => {
|
Key::Enter => {
|
||||||
@ -55,11 +41,10 @@ impl Steppable for SynthControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = self.queue.max_size();
|
let frame = Frame::new(SCREEN_WIDTH, SCREEN_HEIGHT, PixelEncoding::RGBA);
|
||||||
let frame = Frame::new(size.0, size.1, PixelEncoding::RGBA);
|
self.frame_sender.add(system.clock, frame);
|
||||||
self.queue.add(system.clock, frame);
|
|
||||||
|
|
||||||
Ok(ClockDuration::from_millis(1))
|
Ok(ClockDuration::from_micros(100))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,21 +83,20 @@ fn main() {
|
|||||||
moa_minifb::run(matches, |host| {
|
moa_minifb::run(matches, |host| {
|
||||||
let mut system = System::default();
|
let mut system = System::default();
|
||||||
|
|
||||||
let queue = FrameQueue::new(384, 128);
|
let (frame_sender, frame_receiver) = host::frame_queue(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
let (sender, receiver) = mpsc::channel();
|
let (key_sender, key_receiver) = host::event_queue();
|
||||||
let control = wrap_transmutable(SynthControl::new(queue.clone(), receiver));
|
let control = wrap_transmutable(SynthControl::new(key_receiver, frame_sender));
|
||||||
system.add_device("control", control)?;
|
system.add_device("control", control)?;
|
||||||
|
|
||||||
let ym_sound = wrap_transmutable(Ym2612::create(host, Frequency::from_hz(7_670_454))?);
|
let ym_sound = wrap_transmutable(Ym2612::new(host, Frequency::from_hz(7_670_454))?);
|
||||||
initialize_ym(ym_sound.clone())?;
|
initialize_ym(ym_sound.clone())?;
|
||||||
system.add_addressable_device(0x00, ym_sound)?;
|
system.add_addressable_device(0x00, ym_sound)?;
|
||||||
|
|
||||||
let sn_sound = wrap_transmutable(Sn76489::create(host, Frequency::from_hz(3_579_545))?);
|
let sn_sound = wrap_transmutable(Sn76489::new(host, Frequency::from_hz(3_579_545))?);
|
||||||
system.add_addressable_device(0x10, sn_sound)?;
|
system.add_addressable_device(0x10, sn_sound)?;
|
||||||
|
|
||||||
host.add_window(Box::new(queue))?;
|
host.add_video_source(frame_receiver)?;
|
||||||
host.register_keyboard(Box::new(SynthControlsUpdater(sender)))?;
|
host.register_keyboard(key_sender)?;
|
||||||
//host.register_mouse(Box::new(SynthControlsUpdater(sender)))?;
|
|
||||||
|
|
||||||
Ok(system)
|
Ok(system)
|
||||||
});
|
});
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
|
|
||||||
use minifb::Key as MiniKey;
|
use minifb::Key as MiniKey;
|
||||||
use moa_core::host::ControllerEvent;
|
use moa_core::host::ControllerInput;
|
||||||
|
|
||||||
pub fn map_controller_a(key: MiniKey, state: bool) -> Option<ControllerEvent> {
|
pub fn map_controller_a(key: MiniKey, state: bool) -> Option<ControllerInput> {
|
||||||
match key {
|
match key {
|
||||||
MiniKey::A => { Some(ControllerEvent::ButtonA(state)) },
|
MiniKey::A => { Some(ControllerInput::ButtonA(state)) },
|
||||||
MiniKey::O => { Some(ControllerEvent::ButtonB(state)) },
|
MiniKey::O => { Some(ControllerInput::ButtonB(state)) },
|
||||||
MiniKey::E => { Some(ControllerEvent::ButtonC(state)) },
|
MiniKey::E => { Some(ControllerInput::ButtonC(state)) },
|
||||||
MiniKey::Up => { Some(ControllerEvent::DpadUp(state)) },
|
MiniKey::Up => { Some(ControllerInput::DpadUp(state)) },
|
||||||
MiniKey::Down => { Some(ControllerEvent::DpadDown(state)) },
|
MiniKey::Down => { Some(ControllerInput::DpadDown(state)) },
|
||||||
MiniKey::Left => { Some(ControllerEvent::DpadLeft(state)) },
|
MiniKey::Left => { Some(ControllerInput::DpadLeft(state)) },
|
||||||
MiniKey::Right => { Some(ControllerEvent::DpadRight(state)) },
|
MiniKey::Right => { Some(ControllerInput::DpadRight(state)) },
|
||||||
MiniKey::Enter => { Some(ControllerEvent::Start(state)) },
|
MiniKey::Enter => { Some(ControllerInput::Start(state)) },
|
||||||
MiniKey::M => { Some(ControllerEvent::Mode(state)) },
|
MiniKey::M => { Some(ControllerInput::Mode(state)) },
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use minifb::{self, Key, MouseMode, MouseButton};
|
|||||||
use clap::{App, Arg, ArgMatches};
|
use clap::{App, Arg, ArgMatches};
|
||||||
|
|
||||||
use moa_core::{System, Error, ClockDuration};
|
use moa_core::{System, Error, ClockDuration};
|
||||||
use moa_core::host::{Host, ControllerUpdater, KeyboardUpdater, KeyEvent, MouseUpdater, MouseState, Audio, ControllerDevice, PixelEncoding, Frame, FrameReceiver};
|
use moa_core::host::{Host, Audio, KeyEvent, MouseEvent, MouseState, ControllerDevice, ControllerEvent, EventSender, PixelEncoding, Frame, FrameReceiver};
|
||||||
|
|
||||||
use moa_common::{AudioMixer, AudioSource};
|
use moa_common::{AudioMixer, AudioSource};
|
||||||
use moa_common::CpalAudioOutput;
|
use moa_common::CpalAudioOutput;
|
||||||
@ -96,9 +96,9 @@ fn wait_until_initialized(frontend: Arc<Mutex<MiniFrontendBuilder>>) {
|
|||||||
|
|
||||||
pub struct MiniFrontendBuilder {
|
pub struct MiniFrontendBuilder {
|
||||||
video: Option<FrameReceiver>,
|
video: Option<FrameReceiver>,
|
||||||
controller: Option<Box<dyn ControllerUpdater>>,
|
controllers: Option<EventSender<ControllerEvent>>,
|
||||||
keyboard: Option<Box<dyn KeyboardUpdater>>,
|
keyboard: Option<EventSender<KeyEvent>>,
|
||||||
mouse: Option<Box<dyn MouseUpdater>>,
|
mouse: Option<EventSender<MouseEvent>>,
|
||||||
mixer: Option<Arc<Mutex<AudioMixer>>>,
|
mixer: Option<Arc<Mutex<AudioMixer>>>,
|
||||||
finalized: bool,
|
finalized: bool,
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ impl Default for MiniFrontendBuilder {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
video: None,
|
video: None,
|
||||||
controller: None,
|
controllers: None,
|
||||||
keyboard: None,
|
keyboard: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
mixer: Some(AudioMixer::with_default_rate()),
|
mixer: Some(AudioMixer::with_default_rate()),
|
||||||
@ -123,11 +123,11 @@ impl MiniFrontendBuilder {
|
|||||||
|
|
||||||
pub fn build(&mut self) -> MiniFrontend {
|
pub fn build(&mut self) -> MiniFrontend {
|
||||||
let video = std::mem::take(&mut self.video);
|
let video = std::mem::take(&mut self.video);
|
||||||
let controller = std::mem::take(&mut self.controller);
|
let controllers = std::mem::take(&mut self.controllers);
|
||||||
let keyboard = std::mem::take(&mut self.keyboard);
|
let keyboard = std::mem::take(&mut self.keyboard);
|
||||||
let mouse = std::mem::take(&mut self.mouse);
|
let mouse = std::mem::take(&mut self.mouse);
|
||||||
let mixer = std::mem::take(&mut self.mixer);
|
let mixer = std::mem::take(&mut self.mixer);
|
||||||
MiniFrontend::new(video, controller, keyboard, mouse, mixer.unwrap())
|
MiniFrontend::new(video, controllers, keyboard, mouse, mixer.unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,31 +145,27 @@ impl Host for MiniFrontendBuilder {
|
|||||||
Ok(Box::new(source))
|
Ok(Box::new(source))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_controller(&mut self, device: ControllerDevice, input: Box<dyn ControllerUpdater>) -> Result<(), Error> {
|
fn register_controllers(&mut self, sender: EventSender<ControllerEvent>) -> Result<(), Error> {
|
||||||
if device != ControllerDevice::A {
|
if self.controllers.is_some() {
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.controller.is_some() {
|
|
||||||
return Err(Error::new("A controller updater has already been registered with the frontend"));
|
return Err(Error::new("A controller updater has already been registered with the frontend"));
|
||||||
}
|
}
|
||||||
self.controller = Some(input);
|
self.controllers = Some(sender);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_keyboard(&mut self, input: Box<dyn KeyboardUpdater>) -> Result<(), Error> {
|
fn register_keyboard(&mut self, sender: EventSender<KeyEvent>) -> Result<(), Error> {
|
||||||
if self.keyboard.is_some() {
|
if self.keyboard.is_some() {
|
||||||
return Err(Error::new("A keyboard updater has already been registered with the frontend"));
|
return Err(Error::new("A keyboard updater has already been registered with the frontend"));
|
||||||
}
|
}
|
||||||
self.keyboard = Some(input);
|
self.keyboard = Some(sender);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_mouse(&mut self, input: Box<dyn MouseUpdater>) -> Result<(), Error> {
|
fn register_mouse(&mut self, sender: EventSender<MouseEvent>) -> Result<(), Error> {
|
||||||
if self.mouse.is_some() {
|
if self.mouse.is_some() {
|
||||||
return Err(Error::new("A mouse updater has already been registered with the frontend"));
|
return Err(Error::new("A mouse updater has already been registered with the frontend"));
|
||||||
}
|
}
|
||||||
self.mouse = Some(input);
|
self.mouse = Some(sender);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,9 +175,9 @@ pub struct MiniFrontend {
|
|||||||
pub modifiers: u16,
|
pub modifiers: u16,
|
||||||
pub mouse_state: MouseState,
|
pub mouse_state: MouseState,
|
||||||
pub video: Option<FrameReceiver>,
|
pub video: Option<FrameReceiver>,
|
||||||
pub controller: Option<Box<dyn ControllerUpdater>>,
|
pub controllers: Option<EventSender<ControllerEvent>>,
|
||||||
pub keyboard: Option<Box<dyn KeyboardUpdater>>,
|
pub keyboard: Option<EventSender<KeyEvent>>,
|
||||||
pub mouse: Option<Box<dyn MouseUpdater>>,
|
pub mouse: Option<EventSender<MouseEvent>>,
|
||||||
pub audio: Option<CpalAudioOutput>,
|
pub audio: Option<CpalAudioOutput>,
|
||||||
pub mixer: Arc<Mutex<AudioMixer>>,
|
pub mixer: Arc<Mutex<AudioMixer>>,
|
||||||
}
|
}
|
||||||
@ -189,16 +185,16 @@ pub struct MiniFrontend {
|
|||||||
impl MiniFrontend {
|
impl MiniFrontend {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
video: Option<FrameReceiver>,
|
video: Option<FrameReceiver>,
|
||||||
controller: Option<Box<dyn ControllerUpdater>>,
|
controllers: Option<EventSender<ControllerEvent>>,
|
||||||
keyboard: Option<Box<dyn KeyboardUpdater>>,
|
keyboard: Option<EventSender<KeyEvent>>,
|
||||||
mouse: Option<Box<dyn MouseUpdater>>,
|
mouse: Option<EventSender<MouseEvent>>,
|
||||||
mixer: Arc<Mutex<AudioMixer>>,
|
mixer: Arc<Mutex<AudioMixer>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
modifiers: 0,
|
modifiers: 0,
|
||||||
mouse_state: Default::default(),
|
mouse_state: Default::default(),
|
||||||
video,
|
video,
|
||||||
controller,
|
controllers,
|
||||||
keyboard,
|
keyboard,
|
||||||
mouse,
|
mouse,
|
||||||
audio: None,
|
audio: None,
|
||||||
@ -218,7 +214,7 @@ impl MiniFrontend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.occurrences_of("disable-audio") == 0 {
|
if self.mixer.lock().unwrap().num_sources() != 0 && matches.occurrences_of("disable-audio") == 0 {
|
||||||
self.audio = Some(CpalAudioOutput::create_audio_output(self.mixer.lock().unwrap().get_sink()));
|
self.audio = Some(CpalAudioOutput::create_audio_output(self.mixer.lock().unwrap().get_sink()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +288,7 @@ impl MiniFrontend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(updater) = self.mouse.as_mut() {
|
if let Some(sender) = self.mouse.as_mut() {
|
||||||
if let Some((x, y)) = window.get_mouse_pos(MouseMode::Clamp) {
|
if let Some((x, y)) = window.get_mouse_pos(MouseMode::Clamp) {
|
||||||
let left = window.get_mouse_down(MouseButton::Left);
|
let left = window.get_mouse_down(MouseButton::Left);
|
||||||
let right = window.get_mouse_down(MouseButton::Right);
|
let right = window.get_mouse_down(MouseButton::Right);
|
||||||
@ -302,7 +298,7 @@ impl MiniFrontend {
|
|||||||
self.mouse_state
|
self.mouse_state
|
||||||
.to_events(next_state)
|
.to_events(next_state)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|event| updater.update_mouse(event));
|
.for_each(|event| sender.send(event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,13 +312,14 @@ impl MiniFrontend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_key(&mut self, key: Key, state: bool) {
|
fn check_key(&mut self, key: Key, state: bool) {
|
||||||
if let Some(updater) = self.keyboard.as_mut() {
|
if let Some(sender) = self.keyboard.as_mut() {
|
||||||
updater.update_keyboard(KeyEvent::new(map_key(key), state));
|
sender.send(KeyEvent::new(map_key(key), state));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(updater) = self.controller.as_mut() {
|
if let Some(sender) = self.controllers.as_mut() {
|
||||||
if let Some(event) = map_controller_a(key, state) {
|
if let Some(input) = map_controller_a(key, state) {
|
||||||
updater.update_controller(event);
|
let event = ControllerEvent::new(ControllerDevice::A, input);
|
||||||
|
sender.send(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<title>Sega Genesis - Moa</title>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<title>Sega Genesis - Moa</title>
|
|
||||||
<script type="module" src="./interface.js"></script>
|
<script type="module" src="./interface.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="config">
|
<div id="config">
|
||||||
<label>ROM File (only .bin format)</label>
|
<input type="button" id="mute" value="🔈" />
|
||||||
|
<label>ROM File</label>
|
||||||
<input type="file" id="rom-file" accept=".bin,.smd,.md" />
|
<input type="file" id="rom-file" accept=".bin,.smd,.md" />
|
||||||
<input type="button" id="reset" value="Reset" />
|
|
||||||
<input type="button" id="power" value="Power" />
|
<input type="button" id="power" value="Power" />
|
||||||
<input type="text" id="speed" value="4.0" />
|
<input type="button" id="reset" value="Reset" />
|
||||||
<select id="scale">
|
|
||||||
<option value="1">320 x 224</option>
|
|
||||||
<option value="2" selected>640 x 448</option>
|
|
||||||
<option value="3">960 x 672</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="metrics">
|
<div id="metrics">
|
||||||
@ -27,7 +22,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="video-screen">
|
<div id="video-screen">
|
||||||
<canvas id="video" tabindex="0" draw-raw-handle="1" style="width: 640px; height: 448px;" width="640" height="448" />
|
<canvas id="video" tabindex="0" draw-raw-handle="1" />
|
||||||
|
</div>
|
||||||
|
<div id="controller">
|
||||||
|
<button name="left">⇦</button>
|
||||||
|
<button name="up">⇧</button>
|
||||||
|
<button name="down">⇨</button>
|
||||||
|
<button name="right">⇩</button>
|
||||||
|
|
||||||
|
<button name="start">Start</button>
|
||||||
|
|
||||||
|
<button name="a">A</button>
|
||||||
|
<button name="b">B</button>
|
||||||
|
<button name="c">C</button>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -5,6 +5,7 @@ function initialize_emulator() {
|
|||||||
let host = Emulator.new_host();
|
let host = Emulator.new_host();
|
||||||
let system = Emulator.load_system(host, Emulator.get_load_system_fn());
|
let system = Emulator.load_system(host, Emulator.get_load_system_fn());
|
||||||
|
|
||||||
|
//Emulator.start_system(system);
|
||||||
let last_update = performance.now();
|
let last_update = performance.now();
|
||||||
setTimeout(function refreshFrame() {
|
setTimeout(function refreshFrame() {
|
||||||
let current = performance.now();
|
let current = performance.now();
|
||||||
@ -22,6 +23,12 @@ function initialize_emulator() {
|
|||||||
Emulator.host_run_loop(host);
|
Emulator.host_run_loop(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the frame rate display
|
||||||
|
var frame_rate_el = document.getElementById("frame-rate");
|
||||||
|
var frame_rate = setInterval(function () {
|
||||||
|
frame_rate_el.value = Emulator.get_frames_since();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
Emulator.default();
|
Emulator.default();
|
||||||
});
|
});
|
||||||
@ -55,14 +62,31 @@ document.getElementById("power").addEventListener("click", () => {
|
|||||||
initialize_emulator();
|
initialize_emulator();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("speed").addEventListener("change", (e) => {
|
var mute_state = false;
|
||||||
document.getElementById("video").focus();
|
var mute = document.getElementById("mute");
|
||||||
Emulator.set_speed(e.target.value);
|
mute.addEventListener("click", () => {
|
||||||
|
mute_state = !mute_state;
|
||||||
|
if (mute_state) {
|
||||||
|
mute.value = "\uD83D\uDD07";
|
||||||
|
} else {
|
||||||
|
mute.value = "\uD83D\uDD08";
|
||||||
|
}
|
||||||
|
Emulator.set_mute(mute_state);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the frame rate display
|
function button_event(e) {
|
||||||
var frame_rate_el = document.getElementById("frame-rate");
|
var state;
|
||||||
var frame_rate = setInterval(function () {
|
if (e.type == 'mousedown' || e.type == 'touchstart') {
|
||||||
frame_rate_el.value = Emulator.get_frames_since();
|
state = true;
|
||||||
}, 1000);
|
} else {
|
||||||
|
state = false;
|
||||||
|
}
|
||||||
|
Emulator.button_press(e.target.name, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("controller").querySelectorAll('button').forEach(function (button) {
|
||||||
|
button.addEventListener('mousedown', button_event);
|
||||||
|
button.addEventListener('mouseup', button_event);
|
||||||
|
button.addEventListener('touchstart', button_event);
|
||||||
|
button.addEventListener('touchend', button_event);
|
||||||
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
body {
|
body {
|
||||||
|
font-family: sans;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -19,3 +20,16 @@ body {
|
|||||||
color: #DDD;
|
color: #DDD;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mute {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controller .updown {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controller button[name="left"] {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use winit::event::{Event, VirtualKeyCode, WindowEvent, ElementState};
|
|||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
|
||||||
use moa_core::{System, Error};
|
use moa_core::{System, Error};
|
||||||
use moa_core::host::{Host, PixelEncoding, Frame, ControllerDevice, ControllerEvent, ControllerUpdater, Audio, DummyAudio, FrameReceiver};
|
use moa_core::host::{Host, PixelEncoding, Frame, ControllerDevice, ControllerInput, ControllerEvent, EventSender, Audio, DummyAudio, FrameReceiver};
|
||||||
use moa_common::{AudioMixer, AudioSource, CpalAudioOutput};
|
use moa_common::{AudioMixer, AudioSource, CpalAudioOutput};
|
||||||
|
|
||||||
use crate::settings;
|
use crate::settings;
|
||||||
@ -21,22 +21,22 @@ pub type LoadSystemFn = fn (&mut PixelsFrontend, Vec<u8>) -> Result<System, Erro
|
|||||||
|
|
||||||
pub struct PixelsFrontend {
|
pub struct PixelsFrontend {
|
||||||
video: Option<FrameReceiver>,
|
video: Option<FrameReceiver>,
|
||||||
controller: Option<Box<dyn ControllerUpdater>>,
|
controllers: Option<EventSender<ControllerEvent>>,
|
||||||
//mixer: Arc<Mutex<AudioMixer>>,
|
mixer: Arc<Mutex<AudioMixer>>,
|
||||||
//audio_output: CpalAudioOutput,
|
audio_output: CpalAudioOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PixelsFrontend {
|
impl PixelsFrontend {
|
||||||
pub fn new() -> PixelsFrontend {
|
pub fn new() -> PixelsFrontend {
|
||||||
settings::get().run = true;
|
settings::get().run = true;
|
||||||
//let mixer = AudioMixer::with_default_rate();
|
let mixer = AudioMixer::with_default_rate();
|
||||||
//let audio_output = CpalAudioOutput::create_audio_output(mixer.lock().unwrap().get_sink());
|
let audio_output = CpalAudioOutput::create_audio_output(mixer.lock().unwrap().get_sink());
|
||||||
|
|
||||||
PixelsFrontend {
|
PixelsFrontend {
|
||||||
video: None,
|
video: None,
|
||||||
controller: None,
|
controllers: None,
|
||||||
//mixer,
|
mixer,
|
||||||
//audio_output,
|
audio_output,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,19 +47,15 @@ impl Host for PixelsFrontend {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_controller(&mut self, device: ControllerDevice, input: Box<dyn ControllerUpdater>) -> Result<(), Error> {
|
fn register_controllers(&mut self, sender: EventSender<ControllerEvent>) -> Result<(), Error> {
|
||||||
if device != ControllerDevice::A {
|
self.controllers = Some(sender);
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
self.controller = Some(input);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_audio_source(&mut self) -> Result<Box<dyn Audio>, Error> {
|
fn add_audio_source(&mut self) -> Result<Box<dyn Audio>, Error> {
|
||||||
//let source = AudioSource::new(self.mixer.clone());
|
let source = AudioSource::new(self.mixer.clone());
|
||||||
//Ok(Box::new(source))
|
Ok(Box::new(source))
|
||||||
Ok(Box::new(DummyAudio()))
|
//Ok(Box::new(DummyAudio()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,8 +64,8 @@ pub async fn run_loop(host: PixelsFrontend) {
|
|||||||
|
|
||||||
let window = create_window(&event_loop);
|
let window = create_window(&event_loop);
|
||||||
|
|
||||||
if let Some(recevier) = host.video.as_ref() {
|
if let Some(receiver) = host.video.as_ref() {
|
||||||
recevier.request_encoding(PixelEncoding::ABGR);
|
receiver.request_encoding(PixelEncoding::ABGR);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pixels = {
|
let mut pixels = {
|
||||||
@ -81,6 +77,7 @@ pub async fn run_loop(host: PixelsFrontend) {
|
|||||||
.expect("Pixels error")
|
.expect("Pixels error")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut mute = false;
|
||||||
let mut last_size = (WIDTH, HEIGHT);
|
let mut last_size = (WIDTH, HEIGHT);
|
||||||
let mut last_frame = Frame::new(WIDTH, HEIGHT, PixelEncoding::ABGR);
|
let mut last_frame = Frame::new(WIDTH, HEIGHT, PixelEncoding::ABGR);
|
||||||
//let mut update_timer = Instant::now();
|
//let mut update_timer = Instant::now();
|
||||||
@ -131,14 +128,22 @@ pub async fn run_loop(host: PixelsFrontend) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(updater) = host.controller.as_ref() {
|
if let Some(sender) = host.controllers.as_ref() {
|
||||||
if let Some(key) = key {
|
if let Some(key) = key {
|
||||||
updater.update_controller(key);
|
let event = ControllerEvent::new(ControllerDevice::A, key);
|
||||||
|
sender.send(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let requested_mute = settings::get().mute;
|
||||||
|
if requested_mute != mute {
|
||||||
|
mute = requested_mute;
|
||||||
|
host.audio_output.set_mute(mute);
|
||||||
|
log::info!("setting mute to {}", mute);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the run flag is no longer true, and exit the loop
|
// Check if the run flag is no longer true, and exit the loop
|
||||||
if !settings::get().run {
|
if !settings::get().run {
|
||||||
// Clear the screen
|
// Clear the screen
|
||||||
@ -161,17 +166,17 @@ pub async fn run_loop(host: PixelsFrontend) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_controller_a(key: VirtualKeyCode, state: bool) -> Option<ControllerEvent> {
|
pub fn map_controller_a(key: VirtualKeyCode, state: bool) -> Option<ControllerInput> {
|
||||||
match key {
|
match key {
|
||||||
VirtualKeyCode::A => { Some(ControllerEvent::ButtonA(state)) },
|
VirtualKeyCode::A => { Some(ControllerInput::ButtonA(state)) },
|
||||||
VirtualKeyCode::S => { Some(ControllerEvent::ButtonB(state)) },
|
VirtualKeyCode::S => { Some(ControllerInput::ButtonB(state)) },
|
||||||
VirtualKeyCode::D => { Some(ControllerEvent::ButtonC(state)) },
|
VirtualKeyCode::D => { Some(ControllerInput::ButtonC(state)) },
|
||||||
VirtualKeyCode::Up => { Some(ControllerEvent::DpadUp(state)) },
|
VirtualKeyCode::Up => { Some(ControllerInput::DpadUp(state)) },
|
||||||
VirtualKeyCode::Down => { Some(ControllerEvent::DpadDown(state)) },
|
VirtualKeyCode::Down => { Some(ControllerInput::DpadDown(state)) },
|
||||||
VirtualKeyCode::Left => { Some(ControllerEvent::DpadLeft(state)) },
|
VirtualKeyCode::Left => { Some(ControllerInput::DpadLeft(state)) },
|
||||||
VirtualKeyCode::Right => { Some(ControllerEvent::DpadRight(state)) },
|
VirtualKeyCode::Right => { Some(ControllerInput::DpadRight(state)) },
|
||||||
VirtualKeyCode::Return => { Some(ControllerEvent::Start(state)) },
|
VirtualKeyCode::Return => { Some(ControllerInput::Start(state)) },
|
||||||
VirtualKeyCode::M => { Some(ControllerEvent::Mode(state)) },
|
VirtualKeyCode::M => { Some(ControllerInput::Mode(state)) },
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ pub struct EmulatorSettings {
|
|||||||
pub rom_data: Vec<u8>,
|
pub rom_data: Vec<u8>,
|
||||||
pub run: bool,
|
pub run: bool,
|
||||||
pub speed: f32,
|
pub speed: f32,
|
||||||
|
pub size: (u32, u32),
|
||||||
pub frames_since: usize,
|
pub frames_since: usize,
|
||||||
|
pub mute: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmulatorSettings {
|
impl EmulatorSettings {
|
||||||
@ -16,7 +18,9 @@ impl EmulatorSettings {
|
|||||||
rom_data: vec![],
|
rom_data: vec![],
|
||||||
run: false,
|
run: false,
|
||||||
speed: 4.0,
|
speed: 4.0,
|
||||||
|
size: (640, 448),
|
||||||
frames_since: 0,
|
frames_since: 0,
|
||||||
|
mute: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,6 +33,10 @@ pub fn set_rom_data(rom_data: Vec<u8>) {
|
|||||||
get().rom_data = rom_data;
|
get().rom_data = rom_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_size(width: u32, height: u32) {
|
||||||
|
get().size = (width, height);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_frames_since() -> usize {
|
pub fn get_frames_since() -> usize {
|
||||||
let mut options = get();
|
let mut options = get();
|
||||||
let frames_since = options.frames_since;
|
let frames_since = options.frames_since;
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
#![cfg(target_arch = "wasm32")]
|
#![cfg(target_arch = "wasm32")]
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
use instant::Instant;
|
use instant::Instant;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use winit::dpi::LogicalSize;
|
use winit::dpi::LogicalSize;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
use winit::window::{Window, WindowBuilder};
|
use winit::window::{Window, WindowBuilder};
|
||||||
|
|
||||||
|
use web_sys::Event;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
|
||||||
use moa_core::{ClockDuration, System};
|
use moa_core::{ClockDuration, System};
|
||||||
|
|
||||||
use crate::settings;
|
use crate::settings;
|
||||||
@ -26,6 +31,11 @@ pub fn set_rom_data(rom_data: Vec<u8>) {
|
|||||||
settings::set_rom_data(rom_data);
|
settings::set_rom_data(rom_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_size(width: u32, height: u32) {
|
||||||
|
settings::set_size(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn request_stop() {
|
pub fn request_stop() {
|
||||||
settings::request_stop();
|
settings::request_stop();
|
||||||
@ -56,6 +66,17 @@ pub fn get_frames_since() -> usize {
|
|||||||
settings::get_frames_since()
|
settings::get_frames_since()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_mute(mute: bool) {
|
||||||
|
settings::get().mute = mute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn button_press(name: String, state: bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct HostHandle(PixelsFrontend);
|
pub struct HostHandle(PixelsFrontend);
|
||||||
|
|
||||||
@ -115,7 +136,7 @@ pub fn create_window<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
|
|||||||
let size = LogicalSize::new(frontend::WIDTH as f64, frontend::HEIGHT as f64);
|
let size = LogicalSize::new(frontend::WIDTH as f64, frontend::HEIGHT as f64);
|
||||||
WindowBuilder::new()
|
WindowBuilder::new()
|
||||||
.with_canvas(Some(canvas))
|
.with_canvas(Some(canvas))
|
||||||
.with_title("Hello Pixels + Web")
|
.with_title("Moa Emulator")
|
||||||
//.with_inner_size(size)
|
//.with_inner_size(size)
|
||||||
//.with_min_inner_size(size)
|
//.with_min_inner_size(size)
|
||||||
.build(event_loop)
|
.build(event_loop)
|
||||||
@ -171,6 +192,24 @@ pub fn create_window<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn ycombinator<F>(f: &F)
|
||||||
|
where
|
||||||
|
F: Fn(&F)
|
||||||
|
{
|
||||||
|
f(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let closure: Closure<dyn Fn(Event)> = Closure::new(move |_e: Event| {
|
||||||
|
|
||||||
|
});
|
||||||
|
client_window
|
||||||
|
.set_interval_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 17)
|
||||||
|
.unwrap();
|
||||||
|
closure.forget();
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
let mut update_timer = Instant::now();
|
let mut update_timer = Instant::now();
|
||||||
let mut system = load(&mut host, settings::get().rom_data.clone()).unwrap();
|
let mut system = load(&mut host, settings::get().rom_data.clone()).unwrap();
|
||||||
@ -196,6 +235,64 @@ pub fn create_window<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
|
|||||||
closure.forget();
|
closure.forget();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn start_system(handle: SystemHandle) -> Handle {
|
||||||
|
let emulator = Emulator::new(handle.0);
|
||||||
|
set_timeout(emulator.clone(), 17);
|
||||||
|
Handle(emulator)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Handle(Rc<RefCell<Emulator>>);
|
||||||
|
|
||||||
|
pub struct Emulator {
|
||||||
|
running: bool,
|
||||||
|
//frontend: PixelsFrontend,
|
||||||
|
system: System,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Emulator {
|
||||||
|
pub fn new(system: System) -> Rc<RefCell<Self>> {
|
||||||
|
Rc::new(RefCell::new(Self {
|
||||||
|
running: false,
|
||||||
|
system,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(emulator: Rc<RefCell<Emulator>>) {
|
||||||
|
let run_timer = Instant::now();
|
||||||
|
let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as u64;
|
||||||
|
if let Err(err) = emulator.borrow_mut().system.run_for(ClockDuration::from_nanos(nanoseconds_per_frame)) {
|
||||||
|
log::error!("{:?}", err);
|
||||||
|
}
|
||||||
|
log::info!("ran simulation for {:?}ms in {:?}ms", nanoseconds_per_frame / 1_000_000, run_timer.elapsed().as_millis());
|
||||||
|
|
||||||
|
let running = emulator.borrow().running;
|
||||||
|
if running {
|
||||||
|
set_timeout(emulator, 17);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_timeout(emulator: Rc<RefCell<Emulator>>, timeout: i32) {
|
||||||
|
emulator.borrow_mut().running = true;
|
||||||
|
let closure: Closure<dyn Fn(Event)> = Closure::new(move |_e: Event| {
|
||||||
|
update(emulator.clone());
|
||||||
|
});
|
||||||
|
|
||||||
|
let client_window = web_sys::window().unwrap();
|
||||||
|
client_window
|
||||||
|
.set_timeout_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), timeout)
|
||||||
|
.unwrap();
|
||||||
|
closure.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -249,8 +249,7 @@ impl EnvelopeGenerator {
|
|||||||
calculate_rate(self.rates[etype as usize], rate_adjust)
|
calculate_rate(self.rates[etype as usize], rate_adjust)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notify_key_change(&mut self, state: bool, envelope_clock: EnvelopeClock, rate_adjust: usize) {
|
fn notify_key_change(&mut self, state: bool, _envelope_clock: EnvelopeClock, rate_adjust: usize) {
|
||||||
|
|
||||||
if state {
|
if state {
|
||||||
let rate = self.get_scaled_rate(EnvelopeState::Attack, rate_adjust);
|
let rate = self.get_scaled_rate(EnvelopeState::Attack, rate_adjust);
|
||||||
if rate < 62 {
|
if rate < 62 {
|
||||||
@ -262,9 +261,6 @@ impl EnvelopeGenerator {
|
|||||||
} else {
|
} else {
|
||||||
self.envelope_state = EnvelopeState::Release;
|
self.envelope_state = EnvelopeState::Release;
|
||||||
}
|
}
|
||||||
//if self.debug_name == "ch 2, op 1" {
|
|
||||||
//println!("change: {} {:?} {}", state, self.envelope_state, self.envelope);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_envelope(&mut self, envelope_clock: EnvelopeClock, rate_adjust: usize) {
|
fn update_envelope(&mut self, envelope_clock: EnvelopeClock, rate_adjust: usize) {
|
||||||
@ -403,7 +399,7 @@ impl PhaseGenerator {
|
|||||||
let increment = if self.block == 0 {
|
let increment = if self.block == 0 {
|
||||||
increment >> 1
|
increment >> 1
|
||||||
} else {
|
} else {
|
||||||
increment << self.block - 1
|
increment << (self.block - 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply detune
|
// Apply detune
|
||||||
@ -539,7 +535,7 @@ impl Operator {
|
|||||||
// If the original phase was in the negative portion, invert the output
|
// If the original phase was in the negative portion, invert the output
|
||||||
// since the sine wave's second half is a mirror of the first half
|
// since the sine wave's second half is a mirror of the first half
|
||||||
if mod_phase & 0x200 != 0 {
|
if mod_phase & 0x200 != 0 {
|
||||||
output = output * -1;
|
output *= -1;
|
||||||
}
|
}
|
||||||
// The output is now represented with a 16-bit signed number in the range of -0x1FFF and 0x1FFF
|
// The output is now represented with a 16-bit signed number in the range of -0x1FFF and 0x1FFF
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU16, Ordering};
|
|
||||||
|
|
||||||
use moa_core::{warn, info};
|
use moa_core::{warn, info};
|
||||||
use moa_core::{System, Error, ClockTime, ClockDuration, Address, Addressable, Steppable, Transmutable};
|
use moa_core::{System, Error, ClockTime, ClockDuration, Address, Addressable, Steppable, Transmutable};
|
||||||
use moa_core::host::{Host, ControllerUpdater, HostData, ControllerDevice, ControllerEvent};
|
use moa_core::host::{self, Host, HostData, ControllerDevice, ControllerInput, ControllerEvent, EventReceiver};
|
||||||
|
|
||||||
|
|
||||||
const REG_VERSION: Address = 0x01;
|
const REG_VERSION: Address = 0x01;
|
||||||
@ -25,7 +22,7 @@ pub struct GenesisControllerPort {
|
|||||||
/// Data contains bits:
|
/// Data contains bits:
|
||||||
/// 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
|
/// 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
|
||||||
/// X | Y | Z | MODE | START | A | C | B | RIGHT | LEFT | DOWN | UP
|
/// X | Y | Z | MODE | START | A | C | B | RIGHT | LEFT | DOWN | UP
|
||||||
buttons: Arc<AtomicU16>,
|
buttons: u16,
|
||||||
|
|
||||||
ctrl: u8,
|
ctrl: u8,
|
||||||
outputs: u8,
|
outputs: u8,
|
||||||
@ -37,7 +34,7 @@ pub struct GenesisControllerPort {
|
|||||||
impl Default for GenesisControllerPort {
|
impl Default for GenesisControllerPort {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
buttons: Arc::new(AtomicU16::new(0xffff)),
|
buttons: 0xffff,
|
||||||
ctrl: 0,
|
ctrl: 0,
|
||||||
outputs: 0,
|
outputs: 0,
|
||||||
th_count: 0,
|
th_count: 0,
|
||||||
@ -48,7 +45,7 @@ impl Default for GenesisControllerPort {
|
|||||||
|
|
||||||
impl GenesisControllerPort {
|
impl GenesisControllerPort {
|
||||||
pub fn get_data(&mut self) -> u8 {
|
pub fn get_data(&mut self) -> u8 {
|
||||||
let inputs = self.buttons.load(Ordering::Relaxed);
|
let inputs = self.buttons;
|
||||||
let th_state = (self.outputs & 0x40) != 0;
|
let th_state = (self.outputs & 0x40) != 0;
|
||||||
|
|
||||||
match (th_state, self.th_count) {
|
match (th_state, self.th_count) {
|
||||||
@ -88,70 +85,60 @@ impl GenesisControllerPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GenesisControllersUpdater(Arc<AtomicU16>, Arc<AtomicBool>);
|
|
||||||
|
|
||||||
impl ControllerUpdater for GenesisControllersUpdater {
|
|
||||||
fn update_controller(&self, event: ControllerEvent) {
|
|
||||||
let (mask, state) = match event {
|
|
||||||
ControllerEvent::ButtonA(state) => (0x0040, state),
|
|
||||||
ControllerEvent::ButtonB(state) => (0x0010, state),
|
|
||||||
ControllerEvent::ButtonC(state) => (0x0020, state),
|
|
||||||
ControllerEvent::DpadUp(state) => (0x0001, state),
|
|
||||||
ControllerEvent::DpadDown(state) => (0x0002, state),
|
|
||||||
ControllerEvent::DpadLeft(state) => (0x0004, state),
|
|
||||||
ControllerEvent::DpadRight(state) => (0x0008, state),
|
|
||||||
ControllerEvent::Start(state) => (0x0080, state),
|
|
||||||
ControllerEvent::Mode(state) => (0x0100, state),
|
|
||||||
_ => (0x0000, false),
|
|
||||||
};
|
|
||||||
|
|
||||||
let buttons = (self.0.load(Ordering::Acquire) & !mask) | (if !state { mask } else { 0 });
|
|
||||||
self.0.store(buttons, Ordering::Release);
|
|
||||||
if buttons != 0 {
|
|
||||||
self.1.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub struct GenesisControllers {
|
pub struct GenesisControllers {
|
||||||
|
receiver: EventReceiver<ControllerEvent>,
|
||||||
port_1: GenesisControllerPort,
|
port_1: GenesisControllerPort,
|
||||||
port_2: GenesisControllerPort,
|
port_2: GenesisControllerPort,
|
||||||
expansion: GenesisControllerPort,
|
expansion: GenesisControllerPort,
|
||||||
has_changed: Arc<AtomicBool>,
|
|
||||||
interrupt: HostData<bool>,
|
interrupt: HostData<bool>,
|
||||||
reset_timer: ClockDuration,
|
reset_timer: ClockDuration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GenesisControllers {
|
impl GenesisControllers {
|
||||||
fn default() -> Self {
|
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||||
Self {
|
let (sender, receiver) = host::event_queue();
|
||||||
|
host.register_controllers(sender)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
receiver,
|
||||||
port_1: GenesisControllerPort::default(),
|
port_1: GenesisControllerPort::default(),
|
||||||
port_2: GenesisControllerPort::default(),
|
port_2: GenesisControllerPort::default(),
|
||||||
expansion: GenesisControllerPort::default(),
|
expansion: GenesisControllerPort::default(),
|
||||||
has_changed: Arc::new(AtomicBool::new(false)),
|
|
||||||
interrupt: HostData::new(false),
|
interrupt: HostData::new(false),
|
||||||
reset_timer: ClockDuration::ZERO,
|
reset_timer: ClockDuration::ZERO,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GenesisControllers {
|
|
||||||
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
|
||||||
let controller = GenesisControllers::default();
|
|
||||||
|
|
||||||
let controller1 = Box::new(GenesisControllersUpdater(controller.port_1.buttons.clone(), controller.has_changed.clone()));
|
|
||||||
host.register_controller(ControllerDevice::A, controller1)?;
|
|
||||||
let controller2 = Box::new(GenesisControllersUpdater(controller.port_2.buttons.clone(), controller.has_changed.clone()));
|
|
||||||
host.register_controller(ControllerDevice::B, controller2)?;
|
|
||||||
|
|
||||||
Ok(controller)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_interrupt_signal(&self) -> HostData<bool> {
|
pub fn get_interrupt_signal(&self) -> HostData<bool> {
|
||||||
self.interrupt.clone()
|
self.interrupt.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_event(&mut self, event: ControllerEvent) {
|
||||||
|
let (mask, state) = match event.input {
|
||||||
|
ControllerInput::ButtonA(state) => (0x0040, state),
|
||||||
|
ControllerInput::ButtonB(state) => (0x0010, state),
|
||||||
|
ControllerInput::ButtonC(state) => (0x0020, state),
|
||||||
|
ControllerInput::DpadUp(state) => (0x0001, state),
|
||||||
|
ControllerInput::DpadDown(state) => (0x0002, state),
|
||||||
|
ControllerInput::DpadLeft(state) => (0x0004, state),
|
||||||
|
ControllerInput::DpadRight(state) => (0x0008, state),
|
||||||
|
ControllerInput::Start(state) => (0x0080, state),
|
||||||
|
ControllerInput::Mode(state) => (0x0100, state),
|
||||||
|
_ => (0x0000, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
let prev_state = match event.device {
|
||||||
|
ControllerDevice::A => &mut self.port_1.buttons,
|
||||||
|
ControllerDevice::B => &mut self.port_2.buttons,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
*prev_state = (*prev_state & !mask) | (if !state { mask } else { 0 });
|
||||||
|
if *prev_state != 0 {
|
||||||
|
self.interrupt.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Addressable for GenesisControllers {
|
impl Addressable for GenesisControllers {
|
||||||
@ -208,9 +195,8 @@ impl Steppable for GenesisControllers {
|
|||||||
fn step(&mut self, _system: &System) -> Result<ClockDuration, Error> {
|
fn step(&mut self, _system: &System) -> Result<ClockDuration, Error> {
|
||||||
let duration = ClockDuration::from_micros(100); // Update every 100us
|
let duration = ClockDuration::from_micros(100); // Update every 100us
|
||||||
|
|
||||||
if self.has_changed.load(Ordering::Acquire) {
|
while let Some(event) = self.receiver.receive() {
|
||||||
self.has_changed.store(false, Ordering::Release);
|
self.process_event(event);
|
||||||
self.interrupt.set(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.reset_timer += duration;
|
self.reset_timer += duration;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use moa_core::{System, Error, ClockTime, ClockDuration, Address, Addressable, Steppable, Transmutable, debug, warn};
|
use moa_core::{System, Error, ClockTime, ClockDuration, Address, Addressable, Steppable, Transmutable, debug, warn};
|
||||||
use moa_core::host::{self, Host, Frame, FrameSender, KeyboardUpdater, KeyEvent};
|
use moa_core::host::{self, Host, Frame, FrameSender, KeyEvent, EventReceiver};
|
||||||
|
|
||||||
use super::keymap;
|
use super::keymap;
|
||||||
use super::charset::CharacterGenerator;
|
use super::charset::CharacterGenerator;
|
||||||
@ -11,38 +9,94 @@ use super::charset::CharacterGenerator;
|
|||||||
const DEV_NAME: &str = "model1";
|
const DEV_NAME: &str = "model1";
|
||||||
const SCREEN_SIZE: (u32, u32) = (384, 128);
|
const SCREEN_SIZE: (u32, u32) = (384, 128);
|
||||||
|
|
||||||
pub struct Model1Peripherals {
|
|
||||||
|
pub struct Model1Keyboard {
|
||||||
|
receiver: EventReceiver<KeyEvent>,
|
||||||
|
keyboard_mem: [u8; 8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model1Keyboard {
|
||||||
|
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||||
|
let (sender, receiver) = host::event_queue();
|
||||||
|
host.register_keyboard(sender)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
receiver,
|
||||||
|
keyboard_mem: [0; 8],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Addressable for Model1Keyboard {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
0x420
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&mut self, _clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
||||||
|
if (0x20..=0xA0).contains(&addr) {
|
||||||
|
let offset = addr - 0x20;
|
||||||
|
data[0] = 0;
|
||||||
|
if (offset & 0x01) != 0 { data[0] |= self.keyboard_mem[0]; }
|
||||||
|
if (offset & 0x02) != 0 { data[0] |= self.keyboard_mem[1]; }
|
||||||
|
if (offset & 0x04) != 0 { data[0] |= self.keyboard_mem[2]; }
|
||||||
|
if (offset & 0x08) != 0 { data[0] |= self.keyboard_mem[3]; }
|
||||||
|
if (offset & 0x10) != 0 { data[0] |= self.keyboard_mem[4]; }
|
||||||
|
if (offset & 0x20) != 0 { data[0] |= self.keyboard_mem[5]; }
|
||||||
|
if (offset & 0x40) != 0 { data[0] |= self.keyboard_mem[6]; }
|
||||||
|
if (offset & 0x80) != 0 { data[0] |= self.keyboard_mem[7]; }
|
||||||
|
//info!("{}: read from keyboard {:x} of {:?}", DEV_NAME, addr, data);
|
||||||
|
} else {
|
||||||
|
warn!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr);
|
||||||
|
}
|
||||||
|
debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
||||||
|
warn!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Steppable for Model1Keyboard {
|
||||||
|
fn step(&mut self, _system: &System) -> Result<ClockDuration, Error> {
|
||||||
|
while let Some(event) = self.receiver.receive() {
|
||||||
|
keymap::record_key_press(&mut self.keyboard_mem, event.key, event.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ClockDuration::from_millis(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transmutable for Model1Keyboard {
|
||||||
|
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Model1Video {
|
||||||
frame_sender: FrameSender,
|
frame_sender: FrameSender,
|
||||||
keyboard_mem: Arc<Mutex<[u8; 8]>>,
|
|
||||||
video_mem: [u8; 1024],
|
video_mem: [u8; 1024],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model1Peripherals {
|
impl Model1Video {
|
||||||
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||||
let (frame_sender, frame_receiver) = host::frame_queue(SCREEN_SIZE.0, SCREEN_SIZE.1);
|
let (frame_sender, frame_receiver) = host::frame_queue(SCREEN_SIZE.0, SCREEN_SIZE.1);
|
||||||
let keyboard_mem = Arc::new(Mutex::new([0; 8]));
|
|
||||||
|
|
||||||
host.add_video_source(frame_receiver)?;
|
host.add_video_source(frame_receiver)?;
|
||||||
host.register_keyboard(Box::new(Model1KeyboardUpdater(keyboard_mem.clone())))?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
frame_sender,
|
frame_sender,
|
||||||
keyboard_mem,
|
|
||||||
video_mem: [0x20; 1024],
|
video_mem: [0x20; 1024],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Model1KeyboardUpdater(Arc<Mutex<[u8; 8]>>);
|
impl Steppable for Model1Video {
|
||||||
|
|
||||||
impl KeyboardUpdater for Model1KeyboardUpdater {
|
|
||||||
fn update_keyboard(&self, event: KeyEvent) {
|
|
||||||
println!(">>> {:?}", event.key);
|
|
||||||
keymap::record_key_press(&mut self.0.lock().unwrap(), event.key, event.state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Steppable for Model1Peripherals {
|
|
||||||
fn step(&mut self, system: &System) -> Result<ClockDuration, Error> {
|
fn step(&mut self, system: &System) -> Result<ClockDuration, Error> {
|
||||||
let mut frame = Frame::new(SCREEN_SIZE.0, SCREEN_SIZE.1, self.frame_sender.encoding());
|
let mut frame = Frame::new(SCREEN_SIZE.0, SCREEN_SIZE.1, self.frame_sender.encoding());
|
||||||
for y in 0..16 {
|
for y in 0..16 {
|
||||||
@ -58,45 +112,25 @@ impl Steppable for Model1Peripherals {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Addressable for Model1Peripherals {
|
impl Addressable for Model1Video {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
0x820
|
0x400
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&mut self, _clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
fn read(&mut self, _clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
||||||
if (0x20..=0xA0).contains(&addr) {
|
data[0] = self.video_mem[addr as usize];
|
||||||
let offset = addr - 0x20;
|
|
||||||
data[0] = 0;
|
|
||||||
if (offset & 0x01) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[0]; }
|
|
||||||
if (offset & 0x02) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[1]; }
|
|
||||||
if (offset & 0x04) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[2]; }
|
|
||||||
if (offset & 0x08) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[3]; }
|
|
||||||
if (offset & 0x10) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[4]; }
|
|
||||||
if (offset & 0x20) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[5]; }
|
|
||||||
if (offset & 0x40) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[6]; }
|
|
||||||
if (offset & 0x80) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[7]; }
|
|
||||||
//info!("{}: read from keyboard {:x} of {:?}", DEV_NAME, addr, data);
|
|
||||||
} else if (0x420..=0x820).contains(&addr) {
|
|
||||||
data[0] = self.video_mem[addr as usize - 0x420];
|
|
||||||
} else {
|
|
||||||
warn!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr);
|
|
||||||
}
|
|
||||||
debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data);
|
debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, _clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
fn write(&mut self, _clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
||||||
debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]);
|
debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]);
|
||||||
if (0x420..0x820).contains(&addr) {
|
self.video_mem[addr as usize] = data[0];
|
||||||
self.video_mem[addr as usize - 0x420] = data[0];
|
|
||||||
} else {
|
|
||||||
warn!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transmutable for Model1Peripherals {
|
impl Transmutable for Model1Video {
|
||||||
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use moa_core::host::Host;
|
|||||||
|
|
||||||
use moa_z80::{Z80, Z80Type};
|
use moa_z80::{Z80, Z80Type};
|
||||||
|
|
||||||
use crate::peripherals::model1::Model1Peripherals;
|
use crate::peripherals::model1::{Model1Keyboard, Model1Video};
|
||||||
|
|
||||||
|
|
||||||
pub struct Trs80Options {
|
pub struct Trs80Options {
|
||||||
@ -37,8 +37,10 @@ pub fn build_trs80<H: Host>(host: &mut H, options: Trs80Options) -> Result<Syste
|
|||||||
let ram = MemoryBlock::new(vec![0; options.memory as usize]);
|
let ram = MemoryBlock::new(vec![0; options.memory as usize]);
|
||||||
system.add_addressable_device(0x4000, wrap_transmutable(ram))?;
|
system.add_addressable_device(0x4000, wrap_transmutable(ram))?;
|
||||||
|
|
||||||
let model1 = Model1Peripherals::new(host)?;
|
let keyboard = Model1Keyboard::new(host)?;
|
||||||
system.add_addressable_device(0x37E0, wrap_transmutable(model1)).unwrap();
|
system.add_addressable_device(0x37E0, wrap_transmutable(keyboard)).unwrap();
|
||||||
|
let video = Model1Video::new(host)?;
|
||||||
|
system.add_addressable_device(0x37E0 + 0x420, wrap_transmutable(video)).unwrap();
|
||||||
|
|
||||||
let cpu = Z80::new(Z80Type::Z80, options.frequency, BusPort::new(0, 16, 8, system.bus.clone()));
|
let cpu = Z80::new(Z80Type::Z80, options.frequency, BusPort::new(0, 16, 8, system.bus.clone()));
|
||||||
//cpu.add_breakpoint(0x0);
|
//cpu.add_breakpoint(0x0);
|
||||||
|
16
todo.txt
16
todo.txt
@ -1,4 +1,20 @@
|
|||||||
|
|
||||||
|
* what if, to allow a device to have multiple steppable functions, you pass the system in, or otherwise provide some mechanism for
|
||||||
|
each device to create sub devices which are scheduled independently
|
||||||
|
|
||||||
|
* for some unknown reason, the js-based updater works much better than the rust based one, but the rust based one just goes back to
|
||||||
|
a fixed time per loop instead of trying to speed up
|
||||||
|
|
||||||
|
* need to remove HostData and replace the interrupt with Signal
|
||||||
|
* make Signal directional, by making SignalDriver and SignalInput or SignalReceiver
|
||||||
|
* should you combine the input updaters into one thing and have a queue for communicating? Even if you have 3 input channels, you can
|
||||||
|
make them all use the same generic type for the input queue
|
||||||
|
* you need to refactor the audio mixer stuff to reject data quicker and stay in sync, but how to deal with sim time-dilation
|
||||||
|
* make the pixels frontend use an rc refcell object, or rc object to directly control the frontend object while also making it accessible
|
||||||
|
to the run loop
|
||||||
|
* add support for the controller inputs in the pixels web frontend
|
||||||
|
* clean up pixels frontend
|
||||||
|
|
||||||
* make the ym generate audio in sync so the DAC timings can be more accurate
|
* make the ym generate audio in sync so the DAC timings can be more accurate
|
||||||
* change the host things to use queues instead
|
* change the host things to use queues instead
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user