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.