Refactor&improve the scheduler to handle missed events
Solves #168 Former-commit-id: 09104a6ebefdaf2e33ae22eb08860d9711ecb66b Former-commit-id: e07219e40fa187f516dd266ab753a67f3c7e796b
This commit is contained in:
parent
7af9bc5760
commit
91453e8777
|
@ -254,7 +254,7 @@ impl EepromChip {
|
|||
result
|
||||
}
|
||||
|
||||
pub(in crate) fn is_transmitting(&self) -> bool {
|
||||
pub(crate) fn is_transmitting(&self) -> bool {
|
||||
use SpiState::*;
|
||||
match self.state {
|
||||
TxData | TxDummy => true,
|
||||
|
@ -262,7 +262,7 @@ impl EepromChip {
|
|||
}
|
||||
}
|
||||
|
||||
pub(in crate) fn reset(&mut self) {
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.state = SpiState::RxInstruction;
|
||||
self.reset_rx_buffer();
|
||||
self.reset_tx_buffer();
|
||||
|
@ -280,7 +280,7 @@ impl EepromChip {
|
|||
/// Eeprom controller can programmed with DMA accesses in 16bit mode
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct EepromController {
|
||||
pub(in crate) chip: RefCell<EepromChip>,
|
||||
pub(crate) chip: RefCell<EepromChip>,
|
||||
detect: bool,
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ pub trait GpioDevice: Sized {
|
|||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Gpio {
|
||||
pub(in crate) rtc: Option<Rtc>,
|
||||
pub(crate) rtc: Option<Rtc>,
|
||||
direction: GpioState,
|
||||
control: GpioPortControl,
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ pub struct Cartridge {
|
|||
size: usize,
|
||||
gpio: Option<Gpio>,
|
||||
symbols: Option<SymbolTable>, // TODO move it somewhere else
|
||||
pub(in crate) backup: BackupMedia,
|
||||
pub(crate) backup: BackupMedia,
|
||||
}
|
||||
|
||||
impl Cartridge {
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::arm7tdmi::memory::{MemoryAccess, MemoryInterface};
|
|||
use super::cartridge::BackupMedia;
|
||||
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
||||
use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
|
||||
use super::sched::{EventType, Scheduler, SchedulerConnect, SharedScheduler};
|
||||
use super::sched::{EventType, Scheduler};
|
||||
use super::sysbus::SysBus;
|
||||
|
||||
use num::FromPrimitive;
|
||||
|
@ -118,7 +118,7 @@ impl DmaChannel {
|
|||
return start_immediately;
|
||||
}
|
||||
|
||||
fn xfer(&mut self, sb: &mut SysBus) {
|
||||
fn transfer(&mut self, sb: &mut SysBus) {
|
||||
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
|
||||
let count = match self.internal.count {
|
||||
0 => match self.id {
|
||||
|
@ -197,9 +197,6 @@ impl DmaChannel {
|
|||
pub struct DmaController {
|
||||
pub channels: [DmaChannel; 4],
|
||||
pending_set: u8,
|
||||
#[serde(skip)]
|
||||
#[serde(default = "Scheduler::new_shared")]
|
||||
scheduler: SharedScheduler,
|
||||
#[cfg(feature = "debugger")]
|
||||
pub trace: bool,
|
||||
}
|
||||
|
@ -212,14 +209,8 @@ impl InterruptConnect for DmaController {
|
|||
}
|
||||
}
|
||||
|
||||
impl SchedulerConnect for DmaController {
|
||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||
self.scheduler = scheduler;
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaController {
|
||||
pub fn new(interrupt_flags: SharedInterruptFlags, scheduler: SharedScheduler) -> DmaController {
|
||||
pub fn new(interrupt_flags: SharedInterruptFlags) -> DmaController {
|
||||
DmaController {
|
||||
channels: [
|
||||
DmaChannel::new(0, interrupt_flags.clone()),
|
||||
|
@ -228,8 +219,6 @@ impl DmaController {
|
|||
DmaChannel::new(3, interrupt_flags.clone()),
|
||||
],
|
||||
pending_set: 0,
|
||||
scheduler: scheduler,
|
||||
|
||||
#[cfg(feature = "debugger")]
|
||||
trace: false,
|
||||
}
|
||||
|
@ -242,13 +231,13 @@ impl DmaController {
|
|||
pub fn perform_work(&mut self, sb: &mut SysBus) {
|
||||
for id in 0..4 {
|
||||
if self.pending_set & (1 << id) != 0 {
|
||||
self.channels[id].xfer(sb);
|
||||
self.channels[id].transfer(sb);
|
||||
}
|
||||
}
|
||||
self.pending_set = 0;
|
||||
}
|
||||
|
||||
pub fn write_16(&mut self, channel_id: usize, ofs: u32, value: u16) {
|
||||
pub fn write_16(&mut self, channel_id: usize, ofs: u32, value: u16, sched: &mut Scheduler) {
|
||||
match ofs {
|
||||
0 => self.channels[channel_id].write_src_low(value),
|
||||
2 => self.channels[channel_id].write_src_high(value),
|
||||
|
@ -262,8 +251,7 @@ impl DmaController {
|
|||
let start_immediately = self.channels[channel_id].write_dma_ctrl(value);
|
||||
if start_immediately {
|
||||
// DMA actually starts after 3 cycles
|
||||
self.scheduler
|
||||
.push(EventType::DmaActivateChannel(channel_id), 3);
|
||||
sched.schedule((EventType::DmaActivateChannel(channel_id), 3));
|
||||
} else {
|
||||
self.deactivate_channel(channel_id);
|
||||
}
|
||||
|
|
|
@ -78,17 +78,24 @@ impl GameBoyAdvance {
|
|||
};
|
||||
|
||||
let interrupt_flags = Rc::new(Cell::new(IrqBitmask(0)));
|
||||
let scheduler = Scheduler::new_shared();
|
||||
let mut scheduler = Scheduler::new_shared();
|
||||
|
||||
let intc = InterruptController::new(interrupt_flags.clone());
|
||||
let gpu = Box::new(Gpu::new(scheduler.clone(), interrupt_flags.clone()));
|
||||
let dmac = DmaController::new(interrupt_flags.clone(), scheduler.clone());
|
||||
let timers = Timers::new(interrupt_flags.clone(), scheduler.clone());
|
||||
let gpu = Box::new(Gpu::new(&mut scheduler, interrupt_flags.clone()));
|
||||
let dmac = DmaController::new(interrupt_flags.clone());
|
||||
let timers = Timers::new(interrupt_flags.clone());
|
||||
let sound_controller = Box::new(SoundController::new(
|
||||
scheduler.clone(),
|
||||
&mut scheduler,
|
||||
audio_device.borrow().get_sample_rate() as f32,
|
||||
));
|
||||
let io_devs = Shared::new(IoDevices::new(intc, gpu, dmac, timers, sound_controller));
|
||||
let io_devs = Shared::new(IoDevices::new(
|
||||
intc,
|
||||
gpu,
|
||||
dmac,
|
||||
timers,
|
||||
sound_controller,
|
||||
scheduler.clone(),
|
||||
));
|
||||
let sysbus = Shared::new(SysBus::new(
|
||||
scheduler.clone(),
|
||||
io_devs.clone(),
|
||||
|
@ -193,7 +200,6 @@ impl GameBoyAdvance {
|
|||
self.sysbus.set_ewram(decoded.ewram);
|
||||
// Redistribute shared pointers
|
||||
self.io_devs.connect_irq(self.interrupt_flags.clone());
|
||||
self.io_devs.connect_scheduler(self.scheduler.clone());
|
||||
self.sysbus.connect_scheduler(self.scheduler.clone());
|
||||
self.sysbus.set_io_devices(self.io_devs.clone());
|
||||
self.sysbus.cartridge.update_from(decoded.cartridge);
|
||||
|
@ -254,7 +260,7 @@ impl GameBoyAdvance {
|
|||
|
||||
// Register an event to mark the end of this run
|
||||
self.scheduler
|
||||
.push(EventType::RunLimitReached, cycles_to_run);
|
||||
.schedule_at(EventType::RunLimitReached, run_start_time + cycles_to_run);
|
||||
|
||||
let mut running = true;
|
||||
while running {
|
||||
|
@ -274,47 +280,52 @@ impl GameBoyAdvance {
|
|||
self.io_devs.haltcnt = HaltState::Running;
|
||||
} else {
|
||||
self.scheduler.fast_forward_to_next();
|
||||
let (event, cycles_late) = self
|
||||
.scheduler
|
||||
.pop_pending_event()
|
||||
.unwrap_or_else(|| unreachable!());
|
||||
self.handle_event(event, cycles_late, &mut running);
|
||||
self.handle_events(&mut running)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((event, cycles_late)) = self.scheduler.pop_pending_event() {
|
||||
self.handle_event(event, cycles_late, &mut running);
|
||||
}
|
||||
self.handle_events(&mut running);
|
||||
}
|
||||
|
||||
let total_cycles_ran = self.scheduler.timestamp() - run_start_time;
|
||||
total_cycles_ran - cycles_to_run
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_event(&mut self, event: EventType, cycles_late: usize, running: &mut bool) {
|
||||
fn handle_events(&mut self, run_limit_flag: &mut bool) {
|
||||
let io = &mut (*self.io_devs);
|
||||
match event {
|
||||
EventType::RunLimitReached => {
|
||||
*running = false;
|
||||
while let Some((event, event_time)) = self.scheduler.pop_pending_event() {
|
||||
// Since we only examine the scheduler queue every so often, most events will be handled late by a few cycles.
|
||||
// We sacrifice accuricy in favor of performance, otherwise we would have to check the event queue
|
||||
// every cpu cycle, where in 99% of cases it will always be empty.
|
||||
let new_event = match event {
|
||||
EventType::RunLimitReached => {
|
||||
*run_limit_flag = false;
|
||||
None
|
||||
}
|
||||
EventType::DmaActivateChannel(channel_id) => {
|
||||
io.dmac.activate_channel(channel_id);
|
||||
None
|
||||
}
|
||||
EventType::TimerOverflow(channel_id) => {
|
||||
let timers = &mut io.timers;
|
||||
let dmac = &mut io.dmac;
|
||||
let apu = &mut io.sound;
|
||||
Some(timers.handle_overflow_event(channel_id, event_time, apu, dmac))
|
||||
}
|
||||
EventType::Gpu(gpu_event) => Some(io.gpu.on_event(
|
||||
gpu_event,
|
||||
&mut *self.sysbus,
|
||||
#[cfg(not(feature = "no_video_interface"))]
|
||||
&self.video_device,
|
||||
)),
|
||||
EventType::Apu(event) => Some(io.sound.on_event(event, &self.audio_device)),
|
||||
};
|
||||
if let Some((new_event, when)) = new_event {
|
||||
// We schedule events added by event handlers relative to the handled event time
|
||||
self.scheduler.schedule_at(new_event, event_time + when)
|
||||
}
|
||||
EventType::DmaActivateChannel(channel_id) => io.dmac.activate_channel(channel_id),
|
||||
EventType::TimerOverflow(channel_id) => {
|
||||
let timers = &mut io.timers;
|
||||
let dmac = &mut io.dmac;
|
||||
let apu = &mut io.sound;
|
||||
timers.handle_overflow_event(channel_id, cycles_late, apu, dmac);
|
||||
}
|
||||
EventType::Gpu(event) => io.gpu.on_event(
|
||||
event,
|
||||
cycles_late,
|
||||
&mut *self.sysbus,
|
||||
#[cfg(not(feature = "no_video_interface"))]
|
||||
&self.video_device,
|
||||
),
|
||||
EventType::Apu(event) => io.sound.on_event(event, cycles_late, &self.audio_device),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||
use super::bus::*;
|
||||
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
|
||||
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
||||
use super::sched::*;
|
||||
use super::sched::{EventType, FutureEvent, GpuEvent, Scheduler};
|
||||
pub use super::sysbus::consts::*;
|
||||
#[cfg(not(feature = "no_video_interface"))]
|
||||
use super::VideoInterface;
|
||||
|
@ -113,11 +113,6 @@ type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
|
|||
pub struct Gpu {
|
||||
interrupt_flags: SharedInterruptFlags,
|
||||
|
||||
/// When deserializing this struct using serde, make sure to call connect_scheduler
|
||||
#[serde(skip)]
|
||||
#[serde(default = "Scheduler::new_shared")]
|
||||
scheduler: SharedScheduler,
|
||||
|
||||
/// how many cycles left until next gpu state ?
|
||||
cycles_left_for_current_state: usize,
|
||||
|
||||
|
@ -153,15 +148,11 @@ impl InterruptConnect for Gpu {
|
|||
}
|
||||
}
|
||||
|
||||
impl SchedulerConnect for Gpu {
|
||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||
self.scheduler = scheduler;
|
||||
}
|
||||
}
|
||||
type FutureGpuEvent = (GpuEvent, usize);
|
||||
|
||||
impl Gpu {
|
||||
pub fn new(mut scheduler: SharedScheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
|
||||
scheduler.push_gpu_event(GpuEvent::HDraw, CYCLES_HDRAW);
|
||||
pub fn new(sched: &mut Scheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
|
||||
sched.schedule((EventType::Gpu(GpuEvent::HDraw), CYCLES_HDRAW));
|
||||
|
||||
fn alloc_scanline_buffer() -> Box<[Rgb15]> {
|
||||
vec![Rgb15::TRANSPARENT; DISPLAY_WIDTH].into_boxed_slice()
|
||||
|
@ -169,7 +160,6 @@ impl Gpu {
|
|||
|
||||
Gpu {
|
||||
interrupt_flags,
|
||||
scheduler,
|
||||
dispcnt: DisplayControl::from(0x80),
|
||||
dispstat: Default::default(),
|
||||
bgcnt: Default::default(),
|
||||
|
@ -367,7 +357,7 @@ impl Gpu {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_hdraw_end<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> (GpuEvent, usize) {
|
||||
fn handle_hdraw_end<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> FutureGpuEvent {
|
||||
self.dispstat.hblank_flag = true;
|
||||
if self.dispstat.hblank_irq_enable {
|
||||
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
||||
|
@ -382,7 +372,7 @@ impl Gpu {
|
|||
&mut self,
|
||||
dma_notifier: &mut D,
|
||||
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
||||
) -> (GpuEvent, usize) {
|
||||
) -> FutureGpuEvent {
|
||||
self.update_vcount(self.vcount + 1);
|
||||
|
||||
if self.vcount < DISPLAY_HEIGHT {
|
||||
|
@ -419,7 +409,7 @@ impl Gpu {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_vblank_hdraw_end(&mut self) -> (GpuEvent, usize) {
|
||||
fn handle_vblank_hdraw_end(&mut self) -> FutureGpuEvent {
|
||||
self.dispstat.hblank_flag = true;
|
||||
if self.dispstat.hblank_irq_enable {
|
||||
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
|
||||
|
@ -427,7 +417,7 @@ impl Gpu {
|
|||
(GpuEvent::VBlankHBlank, CYCLES_HBLANK)
|
||||
}
|
||||
|
||||
fn handle_vblank_hblank_end(&mut self) -> (GpuEvent, usize) {
|
||||
fn handle_vblank_hblank_end(&mut self) -> FutureGpuEvent {
|
||||
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
|
||||
self.update_vcount(self.vcount + 1);
|
||||
self.dispstat.hblank_flag = false;
|
||||
|
@ -444,13 +434,13 @@ impl Gpu {
|
|||
pub fn on_event<D>(
|
||||
&mut self,
|
||||
event: GpuEvent,
|
||||
extra_cycles: usize,
|
||||
dma_notifier: &mut D,
|
||||
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
||||
) where
|
||||
) -> FutureEvent
|
||||
where
|
||||
D: DmaNotifer,
|
||||
{
|
||||
let (next_event, cycles) = match event {
|
||||
let (event, when) = match event {
|
||||
GpuEvent::HDraw => self.handle_hdraw_end(dma_notifier),
|
||||
GpuEvent::HBlank => self.handle_hblank_end(
|
||||
dma_notifier,
|
||||
|
@ -460,8 +450,7 @@ impl Gpu {
|
|||
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw_end(),
|
||||
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
|
||||
};
|
||||
self.scheduler
|
||||
.push(EventType::Gpu(next_event), cycles - extra_cycles);
|
||||
(EventType::Gpu(event), when)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -573,8 +562,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_gpu_state_machine() {
|
||||
let mut sched = Scheduler::new_shared();
|
||||
let mut gpu = Gpu::new(sched.clone(), Rc::new(Cell::new(Default::default())));
|
||||
let mut sched = Scheduler::new();
|
||||
let mut gpu = Gpu::new(&mut sched, Rc::new(Cell::new(Default::default())));
|
||||
#[cfg(not(feature = "no_video_interface"))]
|
||||
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
|
||||
#[cfg(not(feature = "no_video_interface"))]
|
||||
|
|
|
@ -8,7 +8,7 @@ use super::gpu::*;
|
|||
use super::interrupt::{InterruptConnect, InterruptController, SharedInterruptFlags};
|
||||
use super::keypad;
|
||||
use super::mgba_debug::DebugPort;
|
||||
use super::sched::{SchedulerConnect, SharedScheduler};
|
||||
use super::sched::{Scheduler, SchedulerConnect, SharedScheduler};
|
||||
use super::sound::SoundController;
|
||||
use super::sysbus::SysBusPtr;
|
||||
use super::timer::Timers;
|
||||
|
@ -40,6 +40,9 @@ pub struct IoDevices {
|
|||
// HACK
|
||||
// my ownership design sucks
|
||||
#[serde(skip)]
|
||||
#[serde(default = "Scheduler::new_shared")]
|
||||
scheduler: SharedScheduler,
|
||||
#[serde(skip)]
|
||||
#[serde(default = "SysBusPtr::default")]
|
||||
sysbus_ptr: SysBusPtr,
|
||||
}
|
||||
|
@ -51,6 +54,7 @@ impl IoDevices {
|
|||
dmac: DmaController,
|
||||
timers: Timers,
|
||||
sound_controller: Box<SoundController>,
|
||||
scheduler: SharedScheduler,
|
||||
) -> IoDevices {
|
||||
IoDevices {
|
||||
intc,
|
||||
|
@ -63,7 +67,7 @@ impl IoDevices {
|
|||
keyinput: keypad::KEYINPUT_ALL_RELEASED,
|
||||
waitcnt: WaitControl(0),
|
||||
debug: DebugPort::new(),
|
||||
|
||||
scheduler: scheduler,
|
||||
sysbus_ptr: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -84,10 +88,7 @@ impl InterruptConnect for IoDevices {
|
|||
|
||||
impl SchedulerConnect for IoDevices {
|
||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||
self.gpu.connect_scheduler(scheduler.clone());
|
||||
self.sound.connect_scheduler(scheduler.clone());
|
||||
self.dmac.connect_scheduler(scheduler.clone());
|
||||
self.timers.connect_scheduler(scheduler.clone());
|
||||
self.scheduler = scheduler.clone();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,7 @@ impl Bus for IoDevices {
|
|||
REG_IE => io.intc.interrupt_enable.0 as u16,
|
||||
REG_IF => io.intc.interrupt_flags.get().value() as u16,
|
||||
|
||||
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_read(io_addr),
|
||||
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_read(io_addr, &io.scheduler),
|
||||
|
||||
SOUND_BASE..=SOUND_END => io.sound.handle_read(io_addr),
|
||||
REG_DMA0CNT_H => io.dmac.channels[0].ctrl.0,
|
||||
|
@ -263,7 +264,9 @@ impl Bus for IoDevices {
|
|||
REG_IE => io.intc.interrupt_enable.0 = value,
|
||||
REG_IF => io.intc.clear(value),
|
||||
|
||||
REG_TM0CNT_L..=REG_TM3CNT_H => io.timers.handle_write(io_addr, value),
|
||||
REG_TM0CNT_L..=REG_TM3CNT_H => {
|
||||
io.timers.handle_write(io_addr, value, &mut io.scheduler)
|
||||
}
|
||||
|
||||
SOUND_BASE..=SOUND_END => {
|
||||
io.sound.handle_write(io_addr, value);
|
||||
|
@ -272,7 +275,8 @@ impl Bus for IoDevices {
|
|||
DMA_BASE..=REG_DMA3CNT_H => {
|
||||
let ofs = io_addr - DMA_BASE;
|
||||
let channel_id = (ofs / 12) as usize;
|
||||
io.dmac.write_16(channel_id, ofs % 12, value)
|
||||
io.dmac
|
||||
.write_16(channel_id, ofs % 12, value, &mut io.scheduler)
|
||||
}
|
||||
|
||||
REG_WAITCNT => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::cell::Cell;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
|
@ -42,28 +41,22 @@ pub struct Event {
|
|||
typ: EventType,
|
||||
/// Timestamp in cycles
|
||||
time: usize,
|
||||
cancel: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn new(typ: EventType, time: usize) -> Event {
|
||||
Event {
|
||||
typ,
|
||||
time,
|
||||
cancel: Cell::new(false),
|
||||
}
|
||||
pub fn new(typ: EventType, time: usize) -> Event {
|
||||
Event { typ, time }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type(&self) -> EventType {
|
||||
self.typ
|
||||
}
|
||||
|
||||
fn is_canceled(&self) -> bool {
|
||||
self.cancel.get()
|
||||
}
|
||||
}
|
||||
|
||||
/// Future event is an event to be scheduled in x cycles from now
|
||||
pub type FutureEvent = (EventType, usize);
|
||||
|
||||
impl Ord for Event {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.time.cmp(&other.time).reverse()
|
||||
|
@ -140,27 +133,27 @@ impl Scheduler {
|
|||
self.events.peek().map(|e| e.typ)
|
||||
}
|
||||
|
||||
/// Schedule an event to be executed in `cycles` cycles from now
|
||||
pub fn push(&mut self, typ: EventType, cycles: usize) {
|
||||
let event = Event::new(typ, self.timestamp + cycles);
|
||||
/// Schedule an event to be executed in `when` cycles from now
|
||||
pub fn schedule(&mut self, event: FutureEvent) {
|
||||
let (typ, when) = event;
|
||||
let event = Event::new(typ, self.timestamp + when);
|
||||
self.events.push(event);
|
||||
}
|
||||
|
||||
/// Schedule an event to be executed at an exact timestamp, can be used to schedule "past" events.
|
||||
pub fn schedule_at(&mut self, event_typ: EventType, timestamp: usize) {
|
||||
self.events.push(Event::new(event_typ, timestamp));
|
||||
}
|
||||
|
||||
/// Cancel all events with type `typ`
|
||||
/// This method is rather expansive to call
|
||||
pub fn cancel(&mut self, typ: EventType) {
|
||||
/// This method is rather expansive to call since we are reallocating the entire event tree
|
||||
pub fn cancel_pending(&mut self, typ: EventType) {
|
||||
let mut new_events = BinaryHeap::with_capacity(NUM_EVENTS);
|
||||
self.events
|
||||
.iter()
|
||||
.filter(|e| e.typ == typ)
|
||||
.for_each(|e| e.cancel.set(true));
|
||||
}
|
||||
|
||||
pub fn push_gpu_event(&mut self, e: GpuEvent, cycles: usize) {
|
||||
self.push(EventType::Gpu(e), cycles);
|
||||
}
|
||||
|
||||
pub fn push_apu_event(&mut self, e: ApuEvent, cycles: usize) {
|
||||
self.push(EventType::Apu(e), cycles);
|
||||
.filter(|e| e.typ != typ)
|
||||
.for_each(|e| new_events.push(e.clone()));
|
||||
self.events = new_events;
|
||||
}
|
||||
|
||||
/// Updates the scheduler timestamp
|
||||
|
@ -174,11 +167,7 @@ impl Scheduler {
|
|||
if self.timestamp >= event.time {
|
||||
// remove the event
|
||||
let event = self.events.pop().unwrap_or_else(|| unreachable!());
|
||||
if !event.is_canceled() {
|
||||
Some((event.get_type(), self.timestamp - event.time))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some((event.get_type(), event.time))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -63,10 +63,6 @@ type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
|
|||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct SoundController {
|
||||
#[serde(skip)]
|
||||
#[serde(default = "Scheduler::new_shared")]
|
||||
scheduler: SharedScheduler,
|
||||
|
||||
cycles: usize, // cycles count when we last provided a new sample.
|
||||
|
||||
mse: bool,
|
||||
|
@ -105,19 +101,12 @@ pub struct SoundController {
|
|||
output_buffer: Vec<StereoSample<f32>>,
|
||||
}
|
||||
|
||||
impl SchedulerConnect for SoundController {
|
||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||
self.scheduler = scheduler;
|
||||
}
|
||||
}
|
||||
|
||||
impl SoundController {
|
||||
pub fn new(mut scheduler: SharedScheduler, audio_device_sample_rate: f32) -> SoundController {
|
||||
pub fn new(sched: &mut Scheduler, audio_device_sample_rate: f32) -> SoundController {
|
||||
let resampler = CosineResampler::new(32768_f32, audio_device_sample_rate);
|
||||
let cycles_per_sample = 512;
|
||||
scheduler.push(EventType::Apu(ApuEvent::Sample), cycles_per_sample);
|
||||
sched.schedule((EventType::Apu(ApuEvent::Sample), cycles_per_sample));
|
||||
SoundController {
|
||||
scheduler,
|
||||
cycles_per_sample,
|
||||
cycles: 0,
|
||||
mse: false,
|
||||
|
@ -334,7 +323,7 @@ impl SoundController {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn on_sample(&mut self, extra_cycles: usize, audio_device: &AudioDeviceRcRefCell) {
|
||||
fn on_sample(&mut self, audio_device: &AudioDeviceRcRefCell) -> FutureEvent {
|
||||
let mut sample = [0f32; 2];
|
||||
|
||||
for channel in 0..=1 {
|
||||
|
@ -360,20 +349,17 @@ impl SoundController {
|
|||
(right.round() as i16) * (std::i16::MAX / 512),
|
||||
]);
|
||||
});
|
||||
|
||||
self.scheduler
|
||||
.push_apu_event(ApuEvent::Sample, self.cycles_per_sample - extra_cycles);
|
||||
(EventType::Apu(ApuEvent::Sample), self.cycles_per_sample)
|
||||
}
|
||||
|
||||
pub fn on_event(
|
||||
&mut self,
|
||||
event: ApuEvent,
|
||||
extra_cycles: usize,
|
||||
audio_device: &AudioDeviceRcRefCell,
|
||||
) {
|
||||
) -> FutureEvent {
|
||||
match event {
|
||||
ApuEvent::Sample => self.on_sample(extra_cycles, audio_device),
|
||||
_ => debug!("got {:?} event", event),
|
||||
ApuEvent::Sample => self.on_sample(audio_device),
|
||||
_ => unimplemented!("got {:?} event", event),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,7 +161,8 @@ pub type SysBusPtr = WeakPointer<SysBus>;
|
|||
|
||||
impl SchedulerConnect for SysBus {
|
||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||
self.scheduler = scheduler;
|
||||
self.scheduler = scheduler.clone();
|
||||
self.io.connect_scheduler(scheduler.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::dma::DmaController;
|
||||
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
||||
use super::iodev::consts::*;
|
||||
use super::sched::{EventType, Scheduler, SchedulerConnect, SharedScheduler};
|
||||
use super::sched::{EventType, FutureEvent, Scheduler};
|
||||
use super::sound::SoundController;
|
||||
|
||||
use num::FromPrimitive;
|
||||
|
@ -93,9 +93,6 @@ impl Timer {
|
|||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Timers {
|
||||
#[serde(skip)]
|
||||
#[serde(default = "Scheduler::new_shared")]
|
||||
scheduler: SharedScheduler,
|
||||
timers: [Timer; 4],
|
||||
running_timers: u8,
|
||||
|
||||
|
@ -111,12 +108,6 @@ impl InterruptConnect for Timers {
|
|||
}
|
||||
}
|
||||
|
||||
impl SchedulerConnect for Timers {
|
||||
fn connect_scheduler(&mut self, scheduler: SharedScheduler) {
|
||||
self.scheduler = scheduler;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Timers {
|
||||
type Output = Timer;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
|
@ -131,9 +122,8 @@ impl std::ops::IndexMut<usize> for Timers {
|
|||
}
|
||||
|
||||
impl Timers {
|
||||
pub fn new(interrupt_flags: SharedInterruptFlags, scheduler: SharedScheduler) -> Timers {
|
||||
pub fn new(interrupt_flags: SharedInterruptFlags) -> Timers {
|
||||
Timers {
|
||||
scheduler,
|
||||
timers: [
|
||||
Timer::new(0, interrupt_flags.clone()),
|
||||
Timer::new(1, interrupt_flags.clone()),
|
||||
|
@ -147,16 +137,16 @@ impl Timers {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_timer_event(&mut self, id: usize) {
|
||||
fn prepare_next_overflow_event(&mut self, id: usize, start_time: usize) -> FutureEvent {
|
||||
let timer = &mut self.timers[id];
|
||||
timer.is_scheduled = true;
|
||||
timer.start_time = self.scheduler.timestamp();
|
||||
timer.start_time = start_time;
|
||||
let cycles = (timer.ticks_to_overflow() as usize) << timer.prescalar_shift;
|
||||
self.scheduler.push(EventType::TimerOverflow(id), cycles);
|
||||
(EventType::TimerOverflow(id), cycles)
|
||||
}
|
||||
|
||||
fn cancel_timer_event(&mut self, id: usize) {
|
||||
self.scheduler.cancel(EventType::TimerOverflow(id));
|
||||
fn cancel_timer_event(&mut self, id: usize, sched: &mut Scheduler) {
|
||||
sched.cancel_pending(EventType::TimerOverflow(id));
|
||||
self[id].is_scheduled = false;
|
||||
}
|
||||
|
||||
|
@ -185,22 +175,15 @@ impl Timers {
|
|||
pub fn handle_overflow_event(
|
||||
&mut self,
|
||||
id: usize,
|
||||
extra_cycles: usize,
|
||||
overflow_time: usize,
|
||||
apu: &mut SoundController,
|
||||
dmac: &mut DmaController,
|
||||
) {
|
||||
) -> FutureEvent {
|
||||
self.handle_timer_overflow(id, apu, dmac);
|
||||
|
||||
// TODO: re-use add_timer_event function
|
||||
let timer = &mut self.timers[id];
|
||||
timer.is_scheduled = true;
|
||||
timer.start_time = self.scheduler.timestamp() - extra_cycles;
|
||||
let cycles = (timer.ticks_to_overflow() as usize) << timer.prescalar_shift;
|
||||
self.scheduler
|
||||
.push(EventType::TimerOverflow(id), cycles - extra_cycles);
|
||||
self.prepare_next_overflow_event(id, overflow_time)
|
||||
}
|
||||
|
||||
pub fn write_timer_ctl(&mut self, id: usize, value: u16) {
|
||||
pub fn write_timer_ctl(&mut self, id: usize, value: u16, sched: &mut Scheduler) {
|
||||
let timer = &mut self.timers[id];
|
||||
let new_ctl = TimerCtl(value);
|
||||
#[cfg(feature = "debugger")]
|
||||
|
@ -211,11 +194,11 @@ impl Timers {
|
|||
timer.ctl = new_ctl;
|
||||
if new_enabled && !cascade {
|
||||
self.running_timers |= 1 << id;
|
||||
self.cancel_timer_event(id);
|
||||
self.add_timer_event(id);
|
||||
self.cancel_timer_event(id, sched);
|
||||
sched.schedule(self.prepare_next_overflow_event(id, sched.timestamp()));
|
||||
} else {
|
||||
self.running_timers &= !(1 << id);
|
||||
self.cancel_timer_event(id);
|
||||
self.cancel_timer_event(id, sched);
|
||||
}
|
||||
|
||||
#[cfg(feature = "debugger")]
|
||||
|
@ -231,56 +214,56 @@ impl Timers {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn read_timer_data(&mut self, id: usize) -> u16 {
|
||||
fn read_timer_data(&mut self, id: usize, sched: &Scheduler) -> u16 {
|
||||
let timer = &mut self.timers[id];
|
||||
if timer.is_scheduled {
|
||||
// this timer is controlled by the scheduler so we need to manually calculate
|
||||
// this timer is controlled by the sched so we need to manually calculate
|
||||
// the current value of the counter
|
||||
timer.sync_timer_data(self.scheduler.timestamp());
|
||||
timer.sync_timer_data(sched.timestamp());
|
||||
}
|
||||
|
||||
timer.data
|
||||
}
|
||||
|
||||
pub fn handle_read(&mut self, io_addr: u32) -> u16 {
|
||||
pub fn handle_read(&mut self, io_addr: u32, sched: &Scheduler) -> u16 {
|
||||
match io_addr {
|
||||
REG_TM0CNT_H => self.timers[0].ctl.0,
|
||||
REG_TM1CNT_H => self.timers[1].ctl.0,
|
||||
REG_TM2CNT_H => self.timers[2].ctl.0,
|
||||
REG_TM3CNT_H => self.timers[3].ctl.0,
|
||||
REG_TM0CNT_L => self.read_timer_data(0),
|
||||
REG_TM1CNT_L => self.read_timer_data(1),
|
||||
REG_TM2CNT_L => self.read_timer_data(2),
|
||||
REG_TM3CNT_L => self.read_timer_data(3),
|
||||
REG_TM0CNT_L => self.read_timer_data(0, sched),
|
||||
REG_TM1CNT_L => self.read_timer_data(1, sched),
|
||||
REG_TM2CNT_L => self.read_timer_data(2, sched),
|
||||
REG_TM3CNT_L => self.read_timer_data(3, sched),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_write(&mut self, io_addr: u32, value: u16) {
|
||||
pub fn handle_write(&mut self, io_addr: u32, value: u16, sched: &mut Scheduler) {
|
||||
match io_addr {
|
||||
REG_TM0CNT_L => {
|
||||
self.timers[0].data = value;
|
||||
self.timers[0].initial_data = value;
|
||||
}
|
||||
REG_TM0CNT_H => self.write_timer_ctl(0, value),
|
||||
REG_TM0CNT_H => self.write_timer_ctl(0, value, sched),
|
||||
|
||||
REG_TM1CNT_L => {
|
||||
self.timers[1].data = value;
|
||||
self.timers[1].initial_data = value;
|
||||
}
|
||||
REG_TM1CNT_H => self.write_timer_ctl(1, value),
|
||||
REG_TM1CNT_H => self.write_timer_ctl(1, value, sched),
|
||||
|
||||
REG_TM2CNT_L => {
|
||||
self.timers[2].data = value;
|
||||
self.timers[2].initial_data = value;
|
||||
}
|
||||
REG_TM2CNT_H => self.write_timer_ctl(2, value),
|
||||
REG_TM2CNT_H => self.write_timer_ctl(2, value, sched),
|
||||
|
||||
REG_TM3CNT_L => {
|
||||
self.timers[3].data = value;
|
||||
self.timers[3].initial_data = value;
|
||||
}
|
||||
REG_TM3CNT_H => self.write_timer_ctl(3, value),
|
||||
REG_TM3CNT_H => self.write_timer_ctl(3, value, sched),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue