Emülasyon Döngüsü

Tüm bileşenleri yazdığımıza göre artık emülasyon döngüsünü kurabiliriz. Öncelikle gerekli kullanımları yapalım:

use std::thread::sleep;
use std::time::Duration;

CHIP-8 oyunları kendi işlemci hızında çalışabilecek şekilde programlandığından, modern bir işlemcide çok hızlı çalışacaktır. Bu problemi her bir instructiondan sonra belirli bir süre bekleyerek çözebiliriz. Çalışan thread'ımızda hiç bir şey yapmadan bekleyebileceğimiz sleep fonksiyonunu modülümüze ekledik.

Ardıdan emülasyon metodunu impl Emulator bloğu içerisine oluşturalım:

    pub fn emulate(&mut self) {
        while self.display.is_open() {
            self.display.as_mut().update();

            if let Some(keys) = self.display.as_mut().get_keys() {
                keys.iter().for_each(|key| self.keyboard.press_key(*key));
                if keys.is_empty() {
                    self.keyboard.release_key();
                }
            }

            let instruction = self.instruction_oku().expect("Bilinmeyen Instruction");
            self.instruction_calistir(instruction);

            if self.delay_timer > 0 {
                self.delay_timer -= 1;
            }

            if self.sound_timer > 0 {
                if self.sound_timer == 1 {
                    println!("BEEP!");
                }
                self.sound_timer -= 1;
            }

            sleep(Duration::from_millis(5));
        }
    }

Döngümüz pencere açık olduğu süre boyunca çalışacak.

Display tipine eklemiş olduğumuz ve referanstan-referansa dönüştürme işlemi yapan AsMut özelliği sayesinde, self.display.as_mut() diyerek, pencere yapısına ulaşabiliyoruz. minifb'de yer alan update() metodu, çizim yapmasak bile basılan tuşları alabilmemiz için çalıştırmamız gereken bir metod.

            if let Some(keys) = self.display.as_mut().get_keys() {
                keys.iter().for_each(|key| self.keyboard.press_key(*key));
                if keys.is_empty() {
                    self.keyboard.release_key();
                }
            }

Burada penceremizden basılan tuşların bir listesini alıyoruz. Bu fonksiyon Option dönen bir fonksiyon olduğundan, Rust'a ait if let sözdizimini kullanabiliriz. Bu if bloğu sadece self.display.as_mut().get_keys() metodu Some döndüğünde çalışır. Ardından klavye alanına basılan tuşu aktarıyoruz. get_keys() metodunun boş dönmesi durumunda basılan tuşu geri bırakmak için klavye yapımızda kullandığımız release_key() metodunun çalıştırıyoruz.

            let instruction = self.instruction_oku().expect("Bilinmeyen Instruction");
            self.instruction_calistir(instruction);

Daha önce yazdığımız instructionların okunması ve çalıştırılması işlemi burada gerçekleştiriliyor. expect() metodu Option dönen instruction_oku() metodundan None dönülmesi durumunda, "Bilinmeyen Instruction" hata mesajıyla birlikte panikleyip çıkmamızı sağlıyor.

            if self.delay_timer > 0 {
                self.delay_timer -= 1;
            }

            if self.sound_timer > 0 {
                if self.sound_timer == 1 {
                    println!("BEEP!");
                }
                self.sound_timer -= 1;
            }

            sleep(Duration::from_millis(5));

Son olarak CHIP-8'in timerlarını birer sayı azaltıp, her instructiondan sonra 5 mili saniye bekleyerek döngümüzü tamamlıyoruz. Herhangi bir ses arabirimi kullanmadığımızdan, ses için şimdilik ekrana "BEEP!" mesajını yazdırıyoruz. İsterseniz siz bir ses paketi kullanarak bilgisayardan ses çıkmasını da sağlayabilirsiniz.

main() fonksiyonu

Artik emülatörümüz tüm bileşenleriyle hazır. Tek yapmamız gereken program başladığında çalışacak olan main fonksiyonunu tanımlamak. Bunun için program çalıştığında, program argümanlarını almamıza yarayacak std::env::args fonksiyonunu dahil edelim ve main fonksiyonumuzu yazalım:

use std::env::args;

fn main() {
    Emulator::new()
        .rom_oku(args().nth(1).unwrap_or_else(|| "brix.ch8".to_string()))
        .expect("ROM okurken hata oluştu")
        .emulate();
}

args().nth(1).unwrap_or_else(|| "brix.ch8".to_string()) söz dizimi, program argümanlarından ilkini almaya ve eğer herhangi bir argüman girilmezse de varsayılan olarak brix.ch8 stringini dönmeye yarıyor. Bu sayede emülatörümüz istenildiği taktirde farklı bir ROM'u oynatabilir. args() fonksiyonu String cinsinden değerler döndüğü için "brix.ch8"'in de String'e dönüştürülmesi gerekli. Bu işlem için to_string() metodunu kullandık.

Eğer hatırlarsanız rom_oku() metodunu yazarken instance sahipliğini almıştık ve dönüş değeri olarak yine almış olduğumuz sahipliği dönmüştük. Bu sayede expect() ile hatadan kurtarılan instance'a emulate() metodunu zincir halinde kullanabiliyoruz.