2 puan yazan GN⁺ 2025-07-06 | 1 yorum | WhatsApp'ta paylaş
  • CAMLBOY, OCaml ile geliştirilmiş ve tarayıcıda çalışan bir Game Boy emülatörü
  • OCaml'de orta/büyük ölçekli proje geliştirme ve ileri düzey özellikleri kullanma biçimini pratikte öğrenmek için seçilmiş bir proje
  • Temel yapı, soyutlama, GADT, functor, çalışma zamanında modül değiştirme gibi çeşitli OCaml dil özelliklerini pratik biçimde kullanıyor
  • Tarayıcıda 60FPS ile çalışıyor ve performans iyileştirme süreci, darboğaz analizi ve optimizasyon deneyimini paylaşıyor
  • OCaml ekosistemi, test otomasyonu ve emülatör geliştirmenin pratik becerileri nasıl geliştirdiğini özetliyor

Proje özeti

  • Birkaç ay boyunca CAMLBOY projesi üzerinde çalışarak OCaml ile bir Game Boy emülatörü geliştirildi
  • Demo sayfasında çalıştırılabiliyor ve çeşitli homebrew ROM'lar içeriyor
  • Depo GitHub üzerinde açık olarak paylaşılıyor

OCaml öğrenme motivasyonu ve projenin seçilme nedeni

  • Yeni bir dil öğrenirken, orta/büyük ölçekli kod yazma yöntemleri ve ileri özelliklerin gerçek kullanım biçimleri konusunda sınırlamalar hissedildi
  • Bu sorunu çözmek için gerçek bir proje deneyimine ihtiyaç duyuldu ve bu yüzden Game Boy emülatörü geliştirme seçildi
  • Nedenleri
    • Spesifikasyon net olduğu için uygulama kapsamı belirli
    • Yeterince karmaşık ama birkaç ay içinde tamamlanabilecek ölçekte
    • Kişisel motivasyonu yüksek

Emülatör hedefleri

  • Okunabilirlik ve bakım kolaylığını önceleyen kod yazımı
  • js_of_ocaml ile JavaScript'e derlenip tarayıcıda çalıştırılması
  • Mobil tarayıcılarda da oynanabilir FPS seviyesine ulaşmak
  • Farklı derleyici backend'leri için performans benchmark'ları oluşturmak

Yazının amacı ve ana içeriği

Bu yazının amacı, OCaml ile bir Game Boy emülatörü geliştirme yolculuğunu paylaşmak
Ele alınan konular:

  • Game Boy mimarisine genel bakış
  • Test edilebilir ve yeniden kullanılabilir kodu yapılandırma yöntemleri
  • functor, GADT, birinci sınıf modüller gibi ileri OCaml özelliklerinin pratik kullanımı
  • Performans darboğazlarını bulma, optimizasyon ve iyileştirme deneyimi
  • Genel olarak OCaml hakkındaki düşünceler

Genel yapı ve temel arayüzler

  • CPU, Timer, GPU gibi ana donanımlar senkronize bir saat üzerinden çalışıyor
  • Bus, adrese göre her donanım modülüne veri erişimi/iletimi görevini üstleniyor
  • Her donanım modülü Addressable_intf.S arayüzünü uyguluyor
  • Tüm bus ise Word_addressable_intf.S arayüzünü takip ediyor

Ana döngünün çalışma biçimi

  • Donanım senkronizasyonu için ana döngüde aşağıdaki adımlar tekrar ediliyor
    1. Bir CPU komutu çalıştırılır ve tüketilen çevrim sayısı kaydedilir
    2. Timer, GPU aynı çevrim sayısı kadar ilerletilir
  • Bu yöntemle gerçek donanımın senkronizasyon durumu taklit edilir
  • Uygulama kodu örnekleriyle birlikte açıklanır

8 bit, 16 bit veri okuma/yazma soyutlaması

  • Birçok modül 8 bit veri giriş/çıkış arayüzünü (Addressable_intf.S) uygular
  • 16 bit okuma/yazma genişletmesi Word_addressable_intf.S üzerinden miras alınarak ek işlevlerle sunulur
  • OCaml'in signature ve module type include yaklaşımıyla soyutlama katmanları kurulur

Bus, register ve CPU uygulaması

  • Bus: Her donanım modülüne adres tabanlı yönlendirme yapar, memory map'e göre dallanır
  • Register: 8 bit ve 16 bit register okuma/yazma arayüzleri sağlar
  • CPU: Başlangıçta bus bağımlılığı yüksek olduğu için test etmesi zordu
    • Functor uygulanarak bağımlılıklar soyutlandı ve mock enjekte edilebilir hale geldi
    • Böylece birim testleri yazmak çok daha kolaylaştı

Instruction set'in gösterimi (GADT kullanımı)

  • Game Boy'da hem 8/16 bit komutlar bulunduğundan, instruction tanımında tür güvenliği gerekiyor
  • Basit bir variant yaklaşımı, karmaşık pattern matching sırasında dönüş türü çakışması sorunları çıkarıyor
  • GADT (Generalized Algebraic Data Type) uygulanarak hem giriş hem çıkış türleri güvenli biçimde eşleştirilebiliyor
  • GADT kullanıldığında her instruction'ın parametre türü ve dönüş türü doğru biçimde type inference ile belirlenebiliyor
  • Karmaşık komut desenleri ve parametrelerle güvenli şekilde başa çıkılabiliyor

Cartridge ve çalışma zamanında modül seçimi

  • Game Boy cartridge'leri basit ROM'un ötesinde ek donanım (MBC, timer vb.) içerebilir
  • Her tür için ayrı modül uygulamaları ve çalışma zamanında uygun modülün seçilmesi gerekir
  • Birinci sınıf modüller ile çalışma zamanında modül değiştirme ve genişletilebilirlik sağlanır

Test ve keşif odaklı geliştirme

  • test ROM ve ppx_expect kullanılıyor
    • Özelliğe özel test ROM'ları: aritmetik işlemler, MBC desteği gibi belirli alanları doğrular
    • Başarısızlık durumunda ekran çıktısı gibi net tanılar sunar
  • Entegrasyon testleri, büyük refactor'lar ve yeni özellik eklemelerinde güven sağlar
  • Keşif odaklı geliştirme yaklaşımı uygulanır: test ROM'larıyla tekrar tekrar uygulama ve doğrulama yapılır

Tarayıcı arayüzü ve performans optimizasyonu

  • js_of_ocaml ile kolayca JS build alınabiliyor
  • Brr kütüphanesiyle Javascript DOM API'sine OCaml tarzında güvenli erişim sağlanıyor
  • Başlangıç performansı (20FPS) düşüktü, ancak Chrome profiler ile GPU, timer, Bigstringaf vb. darboğazlar analiz edildi
  • Her modül için optimizasyon commit'leri yapıldı; JS build'de verimsiz inlining devre dışı bırakılarak sonunda 60FPS'e (PC/mobil) ulaşıldı
  • Native build'de performans 1000FPS'e kadar çıkıyor

Benchmark ve donanım karşılaştırması

  • Headless benchmark modu uygulanarak her ortam için FPS ölçümü yapılabiliyor

Emülatör geliştirme ve pratik beceriler

  • Yarışma programlamasına benzer şekilde, net bir spesifikasyonu yorumlama → uygulama → doğrulama döngüsü tekrar ediliyor
  • Spesifikasyon temelli geliştirme/test süreçlerinde gerçekten faydalı bir deneyim sunuyor

Modern OCaml ekosistemi ve araçlardaki gelişmeler

  • dune ile kolay bir build sistemi deneyimi sunuluyor
  • Merlin, OCamlformat vb. ile otomatik tamamlama, kod gezinme ve biçimlendirme kolaylaşıyor
  • setup-ocaml ile GitHub Actions'a da kolayca uygulanabiliyor

Fonksiyonel dillere dair bir not

  • Fonksiyonel dillerin yan etkileri en aza indirmek olarak açıklanmasına kuşkuyla yaklaşılıyor
  • Soyutlamanın altına gizlenmiş mutable durum, performans için aktif biçimde kullanılıyor
  • Yazar; statik türler, pattern matching, modül sistemi ve type inference gibi özellikleri tercih ediyor

Zorluklar ve soyutlama bağımlılığının maliyeti

  • Bağımlılık yönetiminin standartlaşması hâlâ karmaşık ve açıklamalar yetersiz (opam vb.)
  • Modül-functor yapısıyla soyutlama eklendiğinde, tüm bağımlılık katmanının yapısını da değiştirmek gerekiyor
  • OOP'den farklı olarak, soyutlama eklendiğinde üst bağımlı modüllerin yazım biçimini de değiştirmek gerekiyor

Önerilen öğrenme kaynakları

Sonuç

  • CAMLBOY projesi sayesinde OCaml'in ileri özellikleri, test, soyutlama ve tarayıcı uyumluluğu pratik biçimde deneyimlendi
  • Ekosistemdeki gelişmelerle gerçek geliştirme deneyiminden doğan avantajlar ve sınırlamalar net biçimde görüldü
  • Emülatör geliştirmenin orta ve üst seviye geliştiricilerin becerilerini geliştirmede gerçekten faydalı olduğu ortaya kondu

1 yorum

 
GN⁺ 2025-07-06
Hacker News görüşleri
  • Emülatör, sanal makine, bytecode yorumlayıcısı yazmak için belirli bir programlama dilinin daha uygun olduğunu kendinden emin biçimde söyleyebilecek biri var mı merak ediyorum. Burada "daha iyi" derken ölçüt performans ya da uygulama hatalarını azaltmak değil; bizzat uygularken ve keşfederken daha sezgisel olması, daha fazla şey öğretmesi ve uygulama deneyiminin kendisinin tatmin edici ve eğlenceli olması. Örneğin Erlang'ın dağıtık sistemler alanında net bir hedefi var ve o alanın alan bilgisiyle dil tasarımı örtüşüyor; bu yüzden kullanınca hem dağıtık sistemler hem de Erlang'ın kendisi hakkında derin bir anlayış kazanılıyor. Bu şekilde, hedefi "makinenin nasıl çalıştığını kodla ifade etmek" olan bir dil var mı diye merak ediyorum

    • Sistem programlama dilleri olan C, C++, Rust ve Zig'in kişisel olarak en "tatmin edici" seçenekler olduğunu vurgulamak isterim. Bu dillerde veri tipleri (uint8 gibi) bellekteki baytlarla doğrudan eşleşir ve memcpy gibi işlemler de doğrudan blit işlemi gibidir. JavaScript gibi dillerde Number tipini bit işlemleri için bayt gibi kullanmaya çalışırken yaşanan sıkıntılar neredeyse hiç yoktur. JavaScript ile emülatör yapınca bu sorunlarla hemen karşılaşırsınız. Elbette grafik gösterimi ve yeterli bellek desteklendiği sürece her dil aşağı yukarı iş görür; sonuçta da en çok keyfi, en rahat olduğunuz dili seçtiğinizde alırsınız

    • Haskell, DSL'ler ve derleyiciler için gereken veri dönüşümlerinde çok başarılıdır. OCaml, Lisp ve pattern matching ile ADT destekleyen modern diller de uygundur. Modern C++ da variant tipleriyle benzer şeyleri yapmayı deneyebilir ama çok temiz değildir. Gerçekten emülatörde oyun çalıştırmak istiyorsanız C veya C++ standart seçimdir. Rust da muhtemelen fena değildir ama düşük seviyeli bellek manipülasyonunda ne kadar rahat olur emin değilim

    • Emülatör, sanal makine ve bytecode yorumlayıcısı yapmak için özel olarak daha iyi bir dil olmadığı görüşündeyim. Dizi yapısı (rastgele indekse sabit zamanlı erişim) ve bit işlemleri olduğu sürece uygulaması inanılmaz kolaylaşıyor. JIT'yi işin içine katmadığınız sürece fonksiyonel diller de dizileri ve bit işlemlerini destekliyor

    • sml, özellikle de MLTon lehçesini önermek isterim. OCaml'ın iyi olmasının neredeyse tüm nedenlerini paylaşıyor ama bana göre ML ailesindeki diller içinde daha iyi tamamlanmış bir seçenek. OCaml'da özlediğim şey en fazla applicative functor; o da sadece modül yapısında ufak bir fark, çok büyük bir mesele değil

    • Tarayıcı içinde eğlence ve deney odaklı bir şeyse Elm de iyi bir seçenek. Benzer bir proje olarak elmboy'a bakmanızı öneririm

  • Bu yazı yalnızca OCaml değil, aynı zamanda bir Game Boy emülatörünün nasıl uygulandığını da çok dolu biçimde anlatıyor; gerçekten harika bir kaynak. Yazara teşekkür ediyorum. Ayrıca uzun zamandır şu fikrim var: Tarayıcı içinde çalışan bir assembler editörüyle assembler/linker/loader'ı tek bir SPA'de birleştirip herkesin kolayca Game Boy homebrew geliştirmeyi deneyebileceği bir yapı olsa, gömülü geliştirme eğitimi için çok faydalı olurdu

    • rgbds-live projesi bu fikre benziyor ve içinde RGBDS gömülü geliyor. rgbds-live
  • Acaba Game Boy emülatöründe ses uygulamasıyla ilgili bir eğitim arayan başkaları da var mı? Çoğu eğitim sesi anlatmıyor ve kendim uygulamaya çalıştığımda da yalnızca mevcut kaynaklarla hem anlamak hem uygulamak zor olmuştu

    • Resmî bir eğitim değil ama bunu nasıl uyguladığımı özetlediğim 2 slayt paylaşayım: slaytlar Game Boy ses sisteminde 4 kanal var ve her kanal her tick'te 0 ile 15 arasında bir değer üretiyor. Emülatörün bunları toplaması (aritmetik ortalama), 0-255 aralığına ölçeklemesi ve ses tamponuna göndermesi gerekiyor. Tick hızı (4.19MHz) ile ses çıkışı (22kHz gibi) uyuşacak şekilde yaklaşık her 190 tick'te bir değer üretilmeli. Kanal özellikleri bu kaynakta iyi özetlenmiş. 1. ve 2. kanallar kare dalga (0/15 tekrarı), 3. kanal keyfi dalga biçimi (bellekten okuma), 4. kanal ise gürültü, LSFR tabanlı. Örnek kod SoundModeX.java için bakmanızı öneririm

    • Bu kaynak da oldukça iyi

    • Şu YouTube videosu da faydalı olabilir

  • Gerçekten harika bir yazı ve çok havalı bir proje gibi görünüyor

  • Demonun fazla hızlı çalıştığı dikkat çekiyor. Throttle onay kutusu pek işe yaramıyor gibi. Hatta işareti kaldırınca daha da yavaşlıyor hissi veriyor. Throttle açıkken 240fps, kapalıyken 180fps alıyorum. Throttle açık olduğunda gerçek emülatörde 1 saniye olması gereken şey yaklaşık 4 saniye sürüyor gibi görünüyor. Muhtemelen monitörün 240Hz yenileme hızıyla ilgili

    • Büyük ihtimalle sadece requestAnimationFrame() çağrılıyor ve deltaTime hesabı eksik
  • Bence gerçekten çok güzel bir yazı. Bunu paylaştığın için teşekkürler. Rust ile kendi Game Boy emülatörümü yapmayı istememe neden oldu; blog yazısı bana çok ilham verdiği için yer imlerine ekledim

  • Functor ve GADT kullanımına gerçekten çok hoş bir örnek. Bunu CHIP 8 veya NES emülatörleriyle karşılaştırmak isterim; ayrıca CAMLBOY'u ocaml-wasm ile WASM'e taşımak da ilginç olabilir

    • js_of_ocaml'ın yeni WASM backend'i (wasm_of_ocaml) var; yani CAMLBOY'u zaten WASM üzerinde çalıştırabiliyor olmalısınız