2020-10-17 14:22:31 +01:00
|
|
|
use std::cmp::Ordering;
|
|
|
|
use std::collections::BinaryHeap;
|
|
|
|
|
2022-09-04 21:54:44 +01:00
|
|
|
use rustboyadvance_utils::Shared;
|
2020-10-03 20:09:38 +01:00
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
const NUM_EVENTS: usize = 32;
|
|
|
|
|
|
|
|
#[repr(u32)]
|
2020-10-17 14:22:31 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialOrd, PartialEq, Eq, Copy, Clone)]
|
2020-10-03 20:09:38 +01:00
|
|
|
pub enum GpuEvent {
|
|
|
|
HDraw,
|
|
|
|
HBlank,
|
|
|
|
VBlankHDraw,
|
|
|
|
VBlankHBlank,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[repr(u32)]
|
2020-10-17 14:22:31 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialOrd, PartialEq, Eq, Copy, Clone)]
|
2020-10-03 20:09:38 +01:00
|
|
|
pub enum ApuEvent {
|
|
|
|
Psg1Generate,
|
|
|
|
Psg2Generate,
|
|
|
|
Psg3Generate,
|
|
|
|
Psg4Generate,
|
|
|
|
Sample,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[repr(u32)]
|
2020-10-17 14:22:31 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialOrd, PartialEq, Eq, Copy, Clone)]
|
2020-10-03 20:09:38 +01:00
|
|
|
pub enum EventType {
|
2020-10-17 14:22:31 +01:00
|
|
|
RunLimitReached,
|
2020-10-03 20:09:38 +01:00
|
|
|
Gpu(GpuEvent),
|
|
|
|
Apu(ApuEvent),
|
2020-10-10 18:57:00 +01:00
|
|
|
DmaActivateChannel(usize),
|
2020-10-17 14:22:31 +01:00
|
|
|
TimerOverflow(usize),
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Eq)]
|
|
|
|
pub struct Event {
|
2020-10-03 20:09:38 +01:00
|
|
|
typ: EventType,
|
|
|
|
/// Timestamp in cycles
|
|
|
|
time: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Event {
|
2022-09-04 00:49:04 +01:00
|
|
|
pub fn new(typ: EventType, time: usize) -> Event {
|
|
|
|
Event { typ, time }
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
#[inline]
|
2020-10-03 20:09:38 +01:00
|
|
|
fn get_type(&self) -> EventType {
|
|
|
|
self.typ
|
|
|
|
}
|
2020-10-17 14:22:31 +01:00
|
|
|
}
|
|
|
|
|
2022-09-04 00:49:04 +01:00
|
|
|
/// Future event is an event to be scheduled in x cycles from now
|
|
|
|
pub type FutureEvent = (EventType, usize);
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
impl Ord for Event {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
self.time.cmp(&other.time).reverse()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Implement custom reverse ordering
|
|
|
|
impl PartialOrd for Event {
|
|
|
|
#[inline]
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
other.time.partial_cmp(&self.time)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn lt(&self, other: &Self) -> bool {
|
|
|
|
other.time < self.time
|
|
|
|
}
|
|
|
|
#[inline]
|
|
|
|
fn le(&self, other: &Self) -> bool {
|
|
|
|
other.time <= self.time
|
|
|
|
}
|
|
|
|
#[inline]
|
|
|
|
fn gt(&self, other: &Self) -> bool {
|
|
|
|
other.time > self.time
|
|
|
|
}
|
|
|
|
#[inline]
|
|
|
|
fn ge(&self, other: &Self) -> bool {
|
|
|
|
other.time >= self.time
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Event {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.time == other.time
|
|
|
|
}
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
/// Event scheduelr for cycle aware components
|
|
|
|
/// The scheduler should be "shared" to all event generating components.
|
|
|
|
/// Each event generator software component can call Scheduler::schedule to generate an event later in the emulation.
|
|
|
|
/// The scheduler should be updated for each increment in CPU cycles,
|
|
|
|
///
|
|
|
|
/// The main emulation loop can then call Scheduler::process_pending to handle the events.
|
2020-10-03 20:09:38 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub struct Scheduler {
|
|
|
|
timestamp: usize,
|
2020-10-17 14:22:31 +01:00
|
|
|
events: BinaryHeap<Event>,
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-10 19:06:11 +01:00
|
|
|
pub type SharedScheduler = Shared<Scheduler>;
|
2020-10-03 20:09:38 +01:00
|
|
|
|
|
|
|
impl Scheduler {
|
2022-09-04 22:31:38 +01:00
|
|
|
pub fn new() -> Scheduler {
|
|
|
|
Scheduler {
|
2020-10-03 20:09:38 +01:00
|
|
|
timestamp: 0,
|
2020-10-17 14:22:31 +01:00
|
|
|
events: BinaryHeap::with_capacity(NUM_EVENTS),
|
2022-09-04 22:31:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_shared() -> SharedScheduler {
|
|
|
|
Scheduler::new().make_shared()
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn make_shared(self) -> SharedScheduler {
|
2020-10-10 19:06:11 +01:00
|
|
|
SharedScheduler::new(self)
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
2020-11-06 10:38:41 +00:00
|
|
|
#[inline]
|
|
|
|
#[allow(unused)]
|
|
|
|
pub fn num_pending_events(&self) -> usize {
|
|
|
|
self.events.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
#[allow(unused)]
|
|
|
|
pub fn peek_next(&self) -> Option<EventType> {
|
|
|
|
self.events.peek().map(|e| e.typ)
|
|
|
|
}
|
|
|
|
|
2022-09-04 00:49:04 +01:00
|
|
|
/// 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);
|
2020-10-17 14:22:31 +01:00
|
|
|
self.events.push(event);
|
|
|
|
}
|
|
|
|
|
2022-09-04 00:49:04 +01:00
|
|
|
/// 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));
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
/// Cancel all events with type `typ`
|
2022-09-04 00:49:04 +01:00
|
|
|
/// 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);
|
2020-10-17 14:22:31 +01:00
|
|
|
self.events
|
|
|
|
.iter()
|
2022-09-04 00:49:04 +01:00
|
|
|
.filter(|e| e.typ != typ)
|
|
|
|
.for_each(|e| new_events.push(e.clone()));
|
|
|
|
self.events = new_events;
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
/// Updates the scheduler timestamp
|
|
|
|
#[inline]
|
|
|
|
pub fn update(&mut self, cycles: usize) {
|
|
|
|
self.timestamp += cycles;
|
|
|
|
}
|
2020-10-03 20:09:38 +01:00
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
pub fn pop_pending_event(&mut self) -> Option<(EventType, usize)> {
|
|
|
|
if let Some(event) = self.events.peek() {
|
|
|
|
if self.timestamp >= event.time {
|
|
|
|
// remove the event
|
|
|
|
let event = self.events.pop().unwrap_or_else(|| unreachable!());
|
2022-09-04 00:49:04 +01:00
|
|
|
Some((event.get_type(), event.time))
|
2020-10-03 20:09:38 +01:00
|
|
|
} else {
|
2020-10-17 14:22:31 +01:00
|
|
|
None
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
2020-10-17 14:22:31 +01:00
|
|
|
} else {
|
|
|
|
None
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
#[inline]
|
|
|
|
pub fn fast_forward_to_next(&mut self) {
|
|
|
|
self.timestamp += self.get_cycles_to_next_event();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-10-03 20:09:38 +01:00
|
|
|
pub fn get_cycles_to_next_event(&self) -> usize {
|
2020-10-17 14:22:31 +01:00
|
|
|
if let Some(event) = self.events.peek() {
|
|
|
|
event.time - self.timestamp
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
/// The event queue is assumed to be not empty
|
|
|
|
pub fn timestamp_of_next_event(&self) -> usize {
|
|
|
|
self.events.peek().unwrap_or_else(|| unreachable!()).time
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn timestamp(&self) -> usize {
|
|
|
|
self.timestamp
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
self.events.is_empty()
|
|
|
|
}
|
2020-10-17 14:22:31 +01:00
|
|
|
|
|
|
|
pub fn measure_cycles<F: FnMut()>(&mut self, mut f: F) -> usize {
|
|
|
|
let start = self.timestamp;
|
|
|
|
f();
|
|
|
|
self.timestamp - start
|
|
|
|
}
|
2020-10-03 20:09:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-18 16:32:44 +01:00
|
|
|
pub trait SchedulerConnect {
|
|
|
|
fn connect_scheduler(&mut self, scheduler: SharedScheduler);
|
|
|
|
}
|
|
|
|
|
2020-10-03 20:09:38 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
/// Some usecase example where a struct holds the scheduler
|
|
|
|
struct Holder {
|
|
|
|
sched: SharedScheduler,
|
|
|
|
event_bitmask: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
const BIT_GPU_VBLANKHDRAW: u32 = 1 << 0;
|
|
|
|
const BIT_APU_PSG1GENERATE: u32 = 1 << 1;
|
|
|
|
const BIT_APU_PSG2GENERATE: u32 = 1 << 2;
|
|
|
|
const BIT_APU_PSG3GENERATE: u32 = 1 << 3;
|
|
|
|
const BIT_APU_PSG4GENERATE: u32 = 1 << 4;
|
|
|
|
const BIT_APU_SAMPLE: u32 = 1 << 5;
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_event_bit(e: EventType) -> u32 {
|
|
|
|
match e {
|
|
|
|
EventType::Gpu(GpuEvent::VBlankHDraw) => BIT_GPU_VBLANKHDRAW,
|
|
|
|
EventType::Apu(ApuEvent::Psg1Generate) => BIT_APU_PSG1GENERATE,
|
|
|
|
EventType::Apu(ApuEvent::Psg2Generate) => BIT_APU_PSG2GENERATE,
|
|
|
|
EventType::Apu(ApuEvent::Psg3Generate) => BIT_APU_PSG3GENERATE,
|
|
|
|
EventType::Apu(ApuEvent::Psg4Generate) => BIT_APU_PSG4GENERATE,
|
|
|
|
EventType::Apu(ApuEvent::Sample) => BIT_APU_SAMPLE,
|
|
|
|
_ => unimplemented!("unsupported event for this test"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Holder {
|
|
|
|
fn new() -> Holder {
|
|
|
|
Holder {
|
|
|
|
sched: Scheduler::new_shared(),
|
|
|
|
event_bitmask: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_event_done(&self, e: EventType) -> bool {
|
|
|
|
(self.event_bitmask & get_event_bit(e)) != 0
|
|
|
|
}
|
|
|
|
fn handle_event(&mut self, e: EventType, extra_cycles: usize) {
|
|
|
|
println!("[holder] got event {:?} extra_cycles {}", e, extra_cycles);
|
|
|
|
self.event_bitmask |= get_event_bit(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
#[test]
|
|
|
|
fn test_scheduler_ordering() {
|
|
|
|
let mut holder = Holder::new();
|
|
|
|
let mut sched = holder.sched.clone();
|
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Gpu(GpuEvent::VBlankHDraw), 240));
|
2020-10-17 14:22:31 +01:00
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Apu(ApuEvent::Psg1Generate), 60));
|
2020-10-17 14:22:31 +01:00
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Apu(ApuEvent::Sample), 512));
|
2020-10-17 14:22:31 +01:00
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Apu(ApuEvent::Psg2Generate), 13));
|
|
|
|
holder
|
|
|
|
.sched
|
|
|
|
.schedule((EventType::Apu(ApuEvent::Psg4Generate), 72));
|
2020-10-17 14:22:31 +01:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
sched.events.pop(),
|
|
|
|
Some(Event::new(EventType::Apu(ApuEvent::Psg2Generate), 13))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-03 20:09:38 +01:00
|
|
|
#[test]
|
|
|
|
fn test_scheduler() {
|
|
|
|
let mut holder = Holder::new();
|
|
|
|
|
|
|
|
// clone the sched so we get a reference that is not owned by the holder
|
|
|
|
// SAFETY: since the SharedScheduler is built upon an UnsafeCell instead of RefCell, we are sacrificing runtime safety checks for performance.
|
|
|
|
// It is safe since the events iteration allows the EventHandler to modify the queue.
|
|
|
|
|
|
|
|
let mut sched = holder.sched.clone();
|
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Gpu(GpuEvent::VBlankHDraw), 240));
|
|
|
|
holder
|
|
|
|
.sched
|
|
|
|
.schedule((EventType::Apu(ApuEvent::Psg1Generate), 60));
|
2020-10-03 20:09:38 +01:00
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Apu(ApuEvent::Sample), 512));
|
2020-10-03 20:09:38 +01:00
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Apu(ApuEvent::Psg2Generate), 13));
|
2020-10-03 20:09:38 +01:00
|
|
|
holder
|
|
|
|
.sched
|
2022-09-04 21:54:44 +01:00
|
|
|
.schedule((EventType::Apu(ApuEvent::Psg4Generate), 72));
|
2020-10-03 20:09:38 +01:00
|
|
|
|
|
|
|
println!("all events");
|
|
|
|
for e in sched.events.iter() {
|
|
|
|
let typ = e.get_type();
|
|
|
|
println!("{:?}", typ);
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! run_for {
|
|
|
|
($cycles:expr) => {
|
|
|
|
println!("running the scheduler for {} cycles", $cycles);
|
2020-10-17 14:22:31 +01:00
|
|
|
sched.update($cycles);
|
|
|
|
while let Some((event, cycles_late)) = sched.pop_pending_event() {
|
|
|
|
holder.handle_event(event, cycles_late);
|
|
|
|
}
|
2020-10-03 20:09:38 +01:00
|
|
|
if (!sched.is_empty()) {
|
|
|
|
println!(
|
|
|
|
"cycles for next event: {}",
|
|
|
|
sched.get_cycles_to_next_event()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
run_for!(100);
|
|
|
|
|
2020-10-17 14:22:31 +01:00
|
|
|
println!("{:?}", *sched);
|
2020-10-03 20:09:38 +01:00
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Apu(ApuEvent::Psg1Generate)),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Apu(ApuEvent::Psg2Generate)),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Apu(ApuEvent::Psg4Generate)),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Gpu(GpuEvent::VBlankHDraw)),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
run_for!(100);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Gpu(GpuEvent::VBlankHDraw)),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
run_for!(100);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Gpu(GpuEvent::VBlankHDraw)),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
run_for!(211);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
run_for!(1);
|
|
|
|
|
|
|
|
assert_eq!(holder.is_event_done(EventType::Apu(ApuEvent::Sample)), true);
|
|
|
|
|
|
|
|
println!("all events (holder)");
|
|
|
|
for e in holder.sched.events.iter() {
|
|
|
|
let typ = e.get_type();
|
|
|
|
println!("{:?}", typ);
|
|
|
|
}
|
|
|
|
|
|
|
|
println!("all events (cloned again)");
|
|
|
|
let sched_cloned = holder.sched.clone();
|
|
|
|
for e in sched_cloned.events.iter() {
|
|
|
|
let typ = e.get_type();
|
|
|
|
println!("{:?}", typ);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|