summaryrefslogblamecommitdiff
path: root/src/emulator.rs
blob: 4fc1bc4d9d75be04f7c2636509e4453ae7b5f0b9 (plain) (tree)
1
2
3
4
5
6
7
8
9



                    
                         
                 
                       
                                                     


                                                                            

                                   

                       

                          
                          



                                                            
                                                              

                                  

                               

                                         
                              

         





                                                    









                                                                                 





















                                                                          
                                         






                                                
                                            







                                                            
                                                     



                                                 
                                                  

                                                     


                                                                      

                                                   







                                                                     
     
 
                                  
                                                 
     
 
                                                        
                                                          
     
 
                                                          
                                                          
     
 
                                                         
                                                          
     
 
                                                         
                                                      
     
 
                                                       
                                                    
     
 
                                                          
                                                                    
     
 
                                                        
                                                                  
     
 

                                                    
 


                                                           
                                                                               


                        
 



                                                                          
                                                                            


                                                                       








                                                                              
                                        
                                  

                                               
                                                                                  
                                                             
                                                          









                                                                             

                 


                                                   
                                     















                                                                       
use crate::*;

use bedrock_core::*;
use phosphor::*;

use std::cmp::{min, max};
use std::io::Write;
use std::time::*;
use std::thread::sleep;

const FRAME: Duration = Duration::from_micros(16666);
const LINE_HEIGHT: f64 = 20.0;

macro_rules! u16 { ($u32:expr) => { $u32.try_into().unwrap_or(u16::MAX) }; }


pub struct BedrockEmulator {
    vm: Processor<StandardDevices>,
    initialising: bool,
    sleeping: bool,
    pixel_scale: u32,
    process_mark: Instant,
    frame_mark: Instant,
    debug_mark: Instant,
    cycles_elapsed: usize,
}

impl BedrockEmulator {
    pub fn new(bytecode: &[u8]) -> Self {
        let mut vm = Processor::new(StandardDevices::new());
        vm.dev.screen.resize(ScreenDimensions::new(256, 192));
        vm.load_program(bytecode);
        Self {
            vm,
            initialising: true,
            sleeping: false,
            pixel_scale: 3,
            process_mark: Instant::now(),
            frame_mark: Instant::now(),
            debug_mark: Instant::now(),
            cycles_elapsed: 0,
        }
    }

    pub fn run(self) -> ! {
        let mut wm = WindowManager::new();
        wm.add_window(Box::new(self));
        wm.run();
    }

    pub fn debug(&mut self, variant: DebugVariant) {
        macro_rules! yellow {()=>{eprint!("\x1b[33m")};}
        macro_rules! normal {()=>{eprint!("\x1b[0m")};}
        macro_rules! print_stack {
            ($stack:expr, $len:expr) => {
                for i in 0..$len {
                    if i == $stack.sp as usize { yellow!(); } else { normal!(); }
                    eprint!("{:02x} ", $stack.mem[i]);
                }
                normal!();
            };
        }
        match variant {
            DebugVariant::DB1 => {
                eprintln!("\n PC: 0x{:04x}   Cycles: {} (+{} in {:.2?})",
                    self.vm.mem.pc, self.vm.cycles,
                    self.vm.cycles - self.cycles_elapsed,
                    self.debug_mark.elapsed());
                eprint!("WST: ");
                print_stack!(self.vm.wst, 0x10);
                eprint!("\nRST: ");
                print_stack!(self.vm.rst, 0x10);
                eprintln!();
            }
            DebugVariant::DB2 => {
                eprintln!("{:>8.2?}ms ({:>8} cycles)",
                    self.debug_mark.elapsed().as_micros() as f64 / 1000.0,
                    self.vm.cycles - self.cycles_elapsed)
            }
            DebugVariant::DB3 => {
                // Only resets the debug timer
            }
            _ => (),
        }
        self.cycles_elapsed = self.vm.cycles;
        self.debug_mark = Instant::now();
    }
}

impl WindowController for BedrockEmulator {
    fn title(&self) -> String {
        String::from("Bedrock emulator")
    }

    fn exact_size(&self) -> Option<Dimensions> {
        match self.vm.dev.screen.resizable {
            true  => None,
            false => Some(Dimensions::new(
                self.vm.dev.screen.dimensions.width as u32,
                self.vm.dev.screen.dimensions.height as u32,
            )),
        }
    }

    fn is_cursor_visible(&self) -> bool {
        let pos = self.vm.dev.input.pointer_position;
        let dim = self.vm.dev.screen.dimensions;
        pos.x >= dim.width || pos.y >= dim.height
    }

    fn pixel_scale(&self) -> NonZeroU32 {
        NonZeroU32::new(self.pixel_scale).unwrap()
    }

    fn on_resize(&mut self, dimensions: Dimensions) {
        let width = u16!(dimensions.width);
        let height = u16!(dimensions.height);
        self.vm.dev.screen.resize(ScreenDimensions { width, height });
        self.initialising = false;
    }

    fn on_cursor_move(&mut self, position: Point) {
        let x = position.x as u16;
        let y = position.y as u16;
        self.vm.dev.input.on_pointer_move(ScreenPosition::new(x, y));
        self.vm.dev.input.pointer_active = true;
    }

    fn on_cursor_enter(&mut self) {
        self.vm.dev.input.pointer_active = true;
        self.vm.dev.input.wake_flag = true;
    }

    fn on_cursor_exit(&mut self) {
        self.vm.dev.input.pointer_active = false;
        self.vm.dev.input.wake_flag = true;
    }

    fn on_left_mouse_button(&mut self, action: Action) {
        self.vm.dev.input.on_pointer_button(0x80, action);
    }

    fn on_middle_mouse_button(&mut self, action: Action) {
        self.vm.dev.input.on_pointer_button(0x20, action);
    }

    fn on_right_mouse_button(&mut self, action: Action) {
        self.vm.dev.input.on_pointer_button(0x40, action);
    }

    fn on_line_scroll_horizontal(&mut self, delta: f64) {
        self.vm.dev.input.on_scroll_horizontal(delta);
    }

    fn on_line_scroll_vertical(&mut self, delta: f64) {
        self.vm.dev.input.on_scroll_vertical(delta);
    }

    fn on_pixel_scroll_horizontal(&mut self, delta: f64) {
        self.vm.dev.input.on_scroll_horizontal(delta / LINE_HEIGHT);
    }

    fn on_pixel_scroll_vertical(&mut self, delta: f64) {
        self.vm.dev.input.on_scroll_vertical(delta / LINE_HEIGHT);
    }

    fn on_character_input(&mut self, input: char) {
        self.vm.dev.input.on_character_input(input);
    }

    fn on_keyboard_input(&mut self, input: KeyboardInput) {
        self.vm.dev.input.on_keyboard_input(input);
        if input.action.is_pressed() {
            match input.key {
                KeyCode::F5 => self.pixel_scale = max(1, self.pixel_scale - 1),
                KeyCode::F6 => self.pixel_scale = min(8, self.pixel_scale + 1),
                _ => (),
            }
        }
    }

    fn on_keyboard_modifier_change(&mut self, modifiers: ModifiersState) {
        self.vm.dev.input.on_modifier_change(modifiers);
    }

    fn on_process(&mut self) {
        // The duration remaining until the next frame is due to be rendered
        let frame_remaining = match self.frame_mark.elapsed() < FRAME {
            true => FRAME.saturating_sub(self.frame_mark.elapsed()),
            false => FRAME,
        };
        // Wait for the initial resize event to come through
        if self.initialising {
            sleep(FRAME / 10);
            self.process_mark = Instant::now();
            return;
        }
        // Sleep for the remainder of the frame, or until a timer expires
        if self.sleeping && !self.vm.dev.can_wake() {
            let sleep_duration = match self.vm.dev.clock.time_to_next_wake() {
                Some(ms) => min(ms, frame_remaining),
                None => frame_remaining,
            };
            sleep(sleep_duration);
            self.process_mark = Instant::now();
            return;
        }
        // Wake from sleep and evaluate the program for the remainder of the frame
        self.sleeping = false;
        while self.process_mark.elapsed() < frame_remaining {
            if let Some(signal) = self.vm.evaluate(1000) {
                match signal {
                    Signal::Debug(var) => self.debug(var),
                    Signal::Sleep => {
                        self.sleeping = true;
                        let frame_elapsed = self.process_mark.elapsed();
                        sleep(frame_remaining.saturating_sub(frame_elapsed));
                        break;
                    },
                    Signal::Halt => {
                        self.vm.dev.stream.stdout.flush().unwrap();
                        exit(0);
                    },
                }
            }
        }
        self.process_mark = Instant::now();
    }

    fn render_request(&mut self) -> RenderRequest {
        if self.vm.dev.screen.dirty {
            match self.sleeping {
                true  => RenderRequest::UPDATE,
                false => match self.frame_mark.elapsed() >= 6 * FRAME {
                    true  => RenderRequest::UPDATE,
                    false => RenderRequest::NONE,
                }
            }
        } else {
            self.frame_mark = Instant::now();
            RenderRequest::NONE
        }
    }

    fn on_render(&mut self, buffer: &mut Buffer, _hint: RenderHint) {
        self.vm.dev.screen.render(buffer);
        self.frame_mark = Instant::now();
    }
}