Geliştirici günlüğü

Geliştirici Günlüğü #0: Neden Rust ile kendi motorumuz?

Neden Unity, Unreal ya da Godot değil de Rust ile kendi büyük strateji motorumuz? İhtiyaçlar, Rust'ın sağladıkları, ölçülmüş süreler ve bedeli.

İçindekiler
Bir dünya haritasının önünde, dişlilerden ve metal levhalardan tezgâhta kurulan büyük bir makine.

Kısaca: MKA Engine, büyük strateji oyunları için Rust ile yazdığımız kendi simülasyon motorumuz. Hazır motorlar görüntü ve sahneyi iyi çözer; bizim ihtiyacımız ise milyonlarca birimi her oyun gününde hızlı ve her bilgisayarda aynı sonuçla hesaplayan bir çekirdek. Görüntü tarafında Godot kullanacağız; simülasyonu kendimiz yazıyoruz.

MKA Engine'in ilk geliştirici günlüğüne hoş geldiniz. Sıfırıncı günlük, her şeyden önce gelen iki soruya ayrıldı: neden hazır bir oyun motoru kullanmadık da kendi motorumuzu yazıyoruz, ve bu motoru neden Rust ile yazıyoruz? Yazıdaki her iddianın dayanağını yanında ya da yazının sonundaki kaynaklarda bulabilirsiniz.

Hazır motorlar neyi çözüyor, neyi çözmüyor?

Bugün oyun geliştirenlerin çoğu Unity, Unreal ya da Godot gibi hazır bir motorla başlar ve çoğu oyun için bu doğru karardır. Bu motorlar görüntü, ses, fizik, sahne düzenleme araçları ve platform desteği gibi işleri hazır verir.

Büyük strateji oyununun ağırlığı ise başka yerdedir. Ekranda bir harita durur, ama arkasında binlerce bölgenin, yüzlerce ülkenin ve milyonlarca nüfus biriminin her oyun gününde yeniden hesaplandığı bir simülasyon döner. Genel amaçlı motorlar sahne, görüntü ve fizik etrafında kuruludur; bu ölçekte bir veri simülasyonunu oyunun kendisi yazmak zorunda kalır. Türün öncüsü Paradox da oyunlarını kendi geliştirdiği Clausewitz motoruyla yapıyor (Vikipedi, Paradox Development Studio (yeni sekmede açılır)).

İhtiyaçGenel amaçlı hazır motorMKA Engine
OdakGörüntü, fizik, sahneBüyük ölçekli veri simülasyonu
Çok oyunculu oyunSunucu nesnelerin durumunu oyunculara kopyalar (Unreal ağ yapısı (yeni sekmede açılır))Kilit adımlı: yalnız oyuncu komutları gider
Her makinede aynı sonuçOndalıklı sayılarla garanti edilmez (Fiedler (yeni sekmede açılır))Motorun yasası, testlerle denetleniyor
ModlamaOyuna göre değişirOyunun kendisi de bir mod

Kilit adımlı çok oyunculu oyun yeni bir fikir değil: Age of Empires, yüzlerce birimi yavaş modem bağlantılarında bu yöntemle, yalnız komutları göndererek eşitliyordu (1500 Archers on a 28.8 (yeni sekmede açılır)). Yöntemin şartı, her makinenin aynı komutlardan bit bit aynı sonucu üretmesidir; MKA Engine'in bütün tasarımı bu şart üzerine kurulu.

Bu, hazır motorları bir kenara attığımız anlamına gelmiyor. Oyunun ön yüzü, yani harita çizimi, arayüz ve ses için Godot kullanacağız. Simülasyonun kendisi ise MKA Engine'de koşuyor.

Neden Rust?

Motorun dilini seçerken üç şeye baktık: hız, güvenlik ve her platformda aynı davranış. Yıllarca bu türün dili C++ oldu. Rust, C++ ile aynı düzeyde makine koduna derlenen bir dil ve bellek hatalarının büyük bölümünü derleme anında yakalıyor.

1. Hız, çöp toplayıcı olmadan

Rust doğrudan makine koduna derlenir; çalışma anı ya da çöp toplayıcı kullanmaz (rust-lang.org (yeni sekmede açılır)). Çöp toplayıcının oyunun ortasında araya giren duraklamaları, her oyun gününü birkaç milisaniyede hesaplamak isteyen bir motor için kabul edilemez. Bellek ne zaman açılacak, ne zaman bırakılacak, bunu biz belirliyoruz; aşağıda göreceğiniz "gün döngüsünde sıfır bellek ayırma" ölçümü bunun sonucu.

2. Bellek güvenliği

Çelik bir kalkan, parlayan bir bellek bloğunu üzerine gelen kırık parçalardan koruyor; arkada soluk bir dünya haritası var.

C ve C++ ile yazılmış büyük yazılımlarda ciddi hataların büyük kısmı bellek hatalarından gelir: silinmiş bir nesneye erişmek, bir dizinin sınırının dışına yazmak, iki iş parçacığının aynı veriyi aynı anda değiştirmesi gibi. Microsoft her yıl CVE numarası verdiği güvenlik açıklarının yaklaşık %70'inin bellek güvenliği sorunu olduğunu söylüyor (Microsoft Güvenlik Yanıt Merkezi, 2019 (yeni sekmede açılır)); Chromium projesi de yüksek önem dereceli güvenlik hatalarının yaklaşık %70'i için aynı şeyi bildiriyor (Chromium, bellek güvenliği (yeni sekmede açılır)). Rust bu hata sınıflarının büyük bölümünü daha program çalışmadan, derleyicide yakalar. Simülasyonu birden çok iş parçacığına bölerken bu bizim için büyük bir güvence.

3. Her makinede aynı sonuç

Yan yana iki bilgisayar aynı bölge haritasını gösteriyor; aralarında yalnız küçük zarflar, yani komutlar gidip geliyor.

Çok oyunculu oyunda her oyuncunun bilgisayarı aynı dünyayı kendisi hesaplar, ağdan yalnız komutlar gider. Bunun çalışması için aynı komutların her makinede bit bit aynı sonucu vermesi gerekir. Ondalıklı (kayan noktalı) sayılar derleyiciye, derleme ayarlarına ve işlemciye göre küçük farklar üretebilir (Fiedler, Floating Point Determinism (yeni sekmede açılır)). Bu yüzden motorun çekirdeğinde onları hiç kullanmıyoruz; bütün hesaplar sabit noktalı sayılarla yapılıyor.

Rust burada iki kolaylık sağlıyor. Birincisi, kuralları türlerle zorlayabiliyoruz: sabit noktalı sayı ayrı bir tür olduğu için yanlışlıkla ondalıklı sayıyla karıştırılamıyor. İkincisi, aynı kodu farklı hedeflere kolayca derleyebiliyoruz. Çekirdeğin testlerini üç hedefte koşturuyoruz: x86_64 (geliştirme bilgisayarımız), aarch64 (ARM işlemciler; Docker içinde öykünmeyle) ve WebAssembly (wasmtime ile). Altın dosya testleri her çıktıyı depodaki kayıtlı dosyalarla karşılaştırdığı için, üç hedefte de geçmeleri üçünün aynı sonucu ürettiği anlamına geliyor.

4. Modlar için güvenli bir kum havuzu

Modlar WebAssembly'ye derleniyor ve motorun içinde wasmtime ile yalıtılmış bir kum havuzunda çalışıyor (wasmtime güvenlik belgesi (yeni sekmede açılır)). Rust, WebAssembly'yi birinci sınıf hedef olarak destekleyen dillerden biri (Rust ve WebAssembly (yeni sekmede açılır)). Kum havuzundaki bir mod yalnız motorun ona açtığı işlevleri çağırabilir; oyuncunun dosyalarına kendi başına erişemez.

5. Araçlar

Rust'ın paket yöneticisi ve derleme aracı Cargo; derleme, test, belgeleme ve bağımlılık yönetimini tek yerde topluyor (Cargo kitabı (yeni sekmede açılır)). Motorun çekirdek paketleri hiçbir dış kütüphaneye bağlı değil; yalnız birbirlerine bağlılar. Dış kütüphaneler yalnız kenar katmanında, kendi arayüzümüzün arkasında duruyor; lisansları, kaynakları ve güvenlik uyarıları her aşama kapanışında cargo-deny ile denetleniyor (cargo-deny (yeni sekmede açılır)).

Rakamlarla: "hızlı" ne demek?

Hedefimiz, en kalabalık geç oyunda bile bir oyun gününü birkaç milisaniyede hesaplamak. Bunu her aşamada "ölçek dünyası" adını verdiğimiz bir deneme dünyasıyla ölçüyoruz: 30.000 konum, 250 ülke ve 2 milyon nüfus birimi. Hedefimiz bu dünyada bir oyun gününü 8 iş parçacığında en fazla 5 ms'de, tek iş parçacığında en fazla 10 ms'de hesaplamak. Her aşamayı bu hedefe göre ölçüyoruz.

Hedefi neye göre belirledik?

Bu rakamları üç şeye bakarak seçtik:

  • Benzer oyunların ölçeği: Türün bugünkü oyunlarının dünya büyüklüğünü ve yapısını inceledik: kaç konum, kaç ülke, kaç nüfus birimi taşıdıklarını, hangi hesapların her gün, hangilerinin daha seyrek yapıldığını. Ölçek dünyasını bu yüzden bu türün en kalabalık geç oyununa yakın kurduk.
  • Test bilgisayarımız: Ölçümleri AMD Ryzen 7 5700G işlemcili (8 çekirdek, 16 iş parçacığı) geliştirme bilgisayarımızda alıyoruz. Üst sınıf olmayan bir masaüstü işlemci; sonuçlar oyuncuların bilgisayarlarında aynı çıkmayabilir.
  • Ekrana kalan pay: 5 ms'lik bir gün, simülasyonun saniyede en çok 200 oyun günü hesaplayabilmesi demek (1000 ÷ 5; hesap, ölçüm değil). Oyun bu hızın altında aktığı sürece simülasyon saniyenin yalnız bir kısmını kullanır; kalan zaman harita çizimine, arayüze ve yapay zekâya kalır. Tek iş parçacığı hedefi ise çekirdeği az bilgisayarlarda da oyunun akıcı kalması için.

Bir oyun günü kaç milisaniye? (ms / gün)

05101510,322,708,482,061 iş parçacığı8 iş parçacığı
Bir oyun günü kaç milisaniye?
EtiketÖnceSonra
1 iş parçacığı10,328,48
8 iş parçacığı2,702,06
  • Önce
  • Sonra
Ölçek dünyası, geç oyun: 30.000 konum, 250 ülke, 2 milyon nüfus birimi. Küçük olan iyi.Kaynak: performans çalışması, MKA Engine koşucusu, 14 Ekim 2026
ÖlçümHedefÖnceSonraKazançHedefe göre
1 iş parçacığı10 ms10,32 ms8,48 ms%17,8hedefin %15,2 altında
8 iş parçacığı5 ms2,70 ms2,06 ms%23,7hedefin %58,8 altında
Hedefe göre

Son performans çalışmasında 8 iş parçacığında bir gün 2,70 ms'den 2,06 ms'ye, tek iş parçacığında 10,32 ms'den 8,48 ms'ye indi. Çalışmadan önce tek iş parçacığında hedefin %3,2 üstündeydik; şimdi iki ölçüm de hedefin altında. Hızlanma başlıca şu değişikliklerden geldi: pazar hesabında ara sonuçların yeniden kullanılması, sabit noktalı bölmenin hızlandırılması, işlerin iş parçacıklarına daha küçük parçalarla dağıtılması ve uzun işlerin önce başlatılması. Bu sırada önemli bir şey değişmedi: her günün sonunda alınan durum özeti bit bit aynı kaldı ve gün döngüsünde bellek ayırma sıfırda kaldı.

  • 21 dk 35 sn 600 yılın toplam süresi
  • 0 gün döngüsünde bellek ayırma
  • 62 MB tepe bellek kullanımı
  • 0 çökme ve sayı taşması
600 oyun yılı, tek seferde (sabit boyutlu dünya, 8 iş parçacığı)

Uzun koşu da önemli: aynı ölçek dünyasında 600 oyun yılını, yani yaklaşık 219.000 oyun gününü tek seferde koşturduk. Koşu hatasız bitti; gün döngüsünde hiç bellek ayırması olmadı ve tepe bellek kullanımı 62 MB'de kaldı. Rust'ta bellek ayırmayı biz yönettiğimiz için "sıfır ayırma" gibi bir hedefi koyup ölçebiliyoruz.

Bu ölçümün iki sınırı var. Birincisi, deneme dünyası sabit boyutlu: nüfus birimi ve ülke sayısı koşu boyunca değişmiyor, yani dünyanın zamanla büyümesini sınamıyor; o senaryo ayrıca ölçülecek. İkincisi, koşu sırasında bilgisayar başka derlemelerle de meşguldü; bu yüzden gün süreleri koşu içinde dalgalandı ve bu koşunun süreleri yukarıdaki hız ölçümünün yerine geçmez.

Bu rakamlar motorun kendi koşucusundan geliyor; başka motorlarla aynı koşulda bir karşılaştırma yapmadık, o yüzden yanlarına başka bir motorun rakamını koymuyoruz.

Motorun katmanları

Katman katman kesit: en altta dişlilerden oluşan taş bir temel, ortada birbirine geçen yapı taşları, en üstte bayraklı bir dünya haritası.

MKA Engine'i üç katmanlı bir yapı olarak düşünebilirsiniz:

  1. Çekirdek: Sayılar, rastgelelik, verinin bellekte tutulması, zamanlayıcı ve kayıt. Saat, dosya ve ağ buraya giremez.
  2. Mod arayüzü: Oyunun ve modların motorla konuştuğu tek kapı.
  3. Oyun: Tablolar, kurallar, sayılar ve içerik. Oyunun kendisi de bu katmanda duran bir moddur.

Bedeli ne?

Kendi motorunu yazmak ucuz bir karar değil. İlk oynanabilir sürüme hazır bir motorla daha çabuk ulaşılırdı. Sayılardan zamanlayıcıya, kayıttan ağa, mod arayüzünden geliştirici araçlarına kadar her parçayı kendimiz yazıyor, test ediyor ve bakımını kendimiz yapıyoruz; ön yüzle simülasyon arasındaki bağı da kendimiz kuruyoruz. Biz bu bedeli bilerek ödüyoruz, çünkü bu türde temeli sonradan değiştirmek imkânsıza yakındır.

Sık sorulanlar

MKA Engine bir oyun mu?

Hayır. MKA Engine, büyük strateji oyunlarının simülasyonunu hesaplayan bir motor. Görüntüyü çizen ön yüz ve oyunun kendisi ayrı projelerdir; motor bugün geliştirme aşamasında.

Neden Unity, Unreal ya da Godot kullanmadınız?

Bu motorlar görüntü, ses ve sahne araçlarında güçlü. Büyük strateji oyununun ağır kısmı ise milyonlarca satırlık bir dünyayı her gün hesaplamak ve çok oyunculu oyunda her bilgisayarda aynı sonucu üretmek. Bu çekirdeği kendimiz yazıyoruz; görüntü, arayüz ve ses için Godot kullanacağız.

Neden Rust?

Rust bellek hatalarını derleme anında yakalar, çöp toplayıcısı olmadığı için ani duraklamalar yapmaz ve paralel kodda veri yarışlarını derleyici engeller. Ayrıntıları yazının Rust bölümünde bulabilirsiniz.

Motor ne kadar hızlı?

Ölçek dünyasında bir oyun günü tek iş parçacığında 8,48 ms, 8 iş parçacığında 2,06 ms sürdü. Bunlar ekransız koşucuyla alınmış basit ölçümlerdir; görüntü maliyeti dahil değildir.

Kaynaklar ve ölçüm kayıtları

Dış kaynaklar:

İç ölçüm kayıtlarımız (motorun deposu şimdilik kapalı olduğu için bağlantı veremiyoruz):

  • Hız ölçümü: performans çalışması, 14 Ekim 2026. Ölçek dünyası, geç oyun, 1 ve 8 iş parçacığı, AMD Ryzen 7 5700G; her değişiklikten sonra durum özetleri ve gün döngüsündeki bellek ayırma sayısı karşılaştırıldı.
  • 600 yıllık koşu: 14 Ekim 2026, runner --years 600 --threads 8, süre ve tepe bellek /usr/bin/time -v ile ölçüldü.
  • Çapraz sınama: Çekirdek paketler x86_64, aarch64 ve wasm32 hedeflerinde aynı sonuçları veriyor; kayıtlı sonuçlar üç hedefte bit bit aynı.

Bir sonraki günlükte motorun en temel taşına bakacağız: sabit noktalı sayılar. Neden ondalıklı sayı kullanmadığımızı, sayıların bellekte nasıl tutulduğunu ve çarpma, bölme, karekök gibi işlemlerin her makinede aynı sonucu nasıl verdiğini ayrıntısıyla anlatacağız.

Bu günlük hakkında sorularınız ya da önerileriniz varsa bize iletişim sayfasından yazabilirsiniz. Motorun genel tanıtımı için MKA Engine sayfasına, bütün günlükler için Geliştirici Günlükleri listesine bakabilirsiniz.

Kaynaklar ve ilgili belgeler

Bunlar da ilginizi çekebilir