[ARAÇ] SDL3 İlk Adımlar – I

Evet sevgili yazılımperver dostlarım, haftalık yazılarımıza devam ediyoruz. Geçen hafta SDL ve SDL2/SDL3 farklarına kısa bir bakış atmıştık.

[ARAÇ] Merhaba SDL3

Artık bütün bunları ete kemiğe  büründürmenin vakti geldi. Bir süre önce SDL ile ilgili ne var ne yok diye internete baktığımda, SDL2’ye yönelik oldukça fazla kaynak mevcut fakat bunların çoğu genelde küçük küçük kabiliyetlere değinmiş ve uygulama seviyesinde çok bilgi vermiyor gibiydi. SDL3’e yönelik de o kadar çok kaynak olmasa da, durum benzer. Türkçe kaynağın da çok olmaması da ayrı bir konu. Bu sebeple bu ve birkaç yazımı SDL’e ayıracağım.

Kapsam

Daha fazla uzatmadan, bu örneğin amacına değinelim. Bu örneğin amacı, 2025 ve sonrasında modern C++ kullanarak, görsel uygulamalar/oyunlar geliştirebilmeniz için SDL3’e yönelik bir ön izleme sunmak ve bilgilendirmede bulunmak.

Bunu da yaparken, modern C++ ve temel yazılım geliştirme pratikleri, tasarım örüntüleri ve sağlam bir başlangıç noktası oluşturmak olacak. Elbette, alıp hemen bütün ürünlerinizde kullanılabilecek düzeyde olmayabilir, bununla birlikte çok az bir uğraş ile bunu kullanabilir hale getirmenizi hedefliyorum. Bu noktada, okunabilirlik, esneklik ve performans hususları konusunda da bir denge gözetmeye çalışacağım.

Benzeri yazılarda, SDL’e yönelik ilave kabiliyetleri de bu örnek üzerine ekleyerek devam ediyor olacağız.

Haydi kurulum ile başlayalım.

Kurulum/Oluşturma

Bir önceki yazımda bahsettiğim gibi, SDL3 ile birlikte CMake desteği çok daha iyi bir noktaya gelmiş durumda. Bununla birlikte, tek bir yerde birden fazla platform için kurulum yine de zahmetli olabiliyor. Onun için hem Linux hem de Windows için ihtiyaç duyacağınız adımları bu başlık altına ekleyeceğim. SDL2 için gerekli adımları merak ederseniz, uEngine4 reposu içerisinde hem Windows hem de Linux için gerekli adım ve betikleri bulabilirsiniz. Şimdi SDL3’e bakalım.

Öncelikle Linux’e bakalım. Temelde ilgili repoyu indirip, derlemek dışında yapmanız gereken bir şey yok:

Windows için de benzer adımları izleyebilirsiniz.

Evet kurulumları gerçekleştirdikten sonra, örnek uygulamamıza göz atmaya başlayabiliriz. Yukarıdaki adımlara yönelik de betikleri en kısa sürede ekleyeceğim.

Temel Kabiliyetler

Şimdi projemizdeki önemli sınıflara ve bunları geliştirirken izlediğimiz ve kullandığımız yaklaşımlara göz atalım. Bunu yapmadan önce aşağıdaki adresten kodu indirmeyi unutmayın. Şu an kod hem windows hem de linux için derlenebiliyor (derlenmiyorsa, bana ulaşabilirsiniz).

https://github.com/yazilimperver/cpp-playground

Kaynak Yönetimi (RAII Kullanılarak)

Öncelikli olarak SDL kaynak yönetimine bakıyor olacağız. Burada ve benzer birçok yerde RAII prensibini kullanabilirsiniz. Aslında bakarsanız, benzer yaklaşımı OpenGL ve benzeri kütüphaneler için de kolayca kullanabilirsiniz.  Peki RAII nedir?

SDL gibi kütüphaneler yanında C++’ta da bellek, dosya tanıtıcıları, mutex ve benzeri kaynakların yönetimi ve ne zaman silineceğini takip etmek zorlu olabilir. İşte bu noktada,  RAII (Resource Acquisition Is Initialization) prensibi ile kaynak yönetimini hem güvenli hem de otomatik hale getirerek, bu karmaşıklığı büyük ölçüde ortadan kaldırabiliriz. RAII’nin temel fikri, bir kaynağın ömrünü bir nesnenin ömrü ile otomatik olarak hizalamak/bağlamaktır. Şöyle ki; bir nesne oluşturulduğunda kaynak elde edilir, nesne yok edildiğinde ise kaynak otomatik olarak serbest bırakılır/silinir. Aşağıda çok basit bir şekilde bu gösterilmiştir:

Resource Acquisition Is Initialization in Java: Ensuring Safe Resource Management | Java Design Patterns

Modern C++’ta std::unique_ptr, std::shared_ptr, std::lock_guard ve std::fstream gibi sınıflar da RAII prensibini takip ederler. Örneğin std::lock_guard, bir mutex’i kilitleyip otomatik olarak serbest bırakırken, std::unique_ptr dinamik belleği güvenli bir şekilde yönetir. Bu sayede delete veya unlock gibi çağrıları manuel olarak yapmanıza gerek kalmaz, bu da bellek sızıntılarını ve “race condition” gibi durumları önlemeye yardımcı olur.

RAII prensibi sadece daha temiz ve okunabilir kod üretmekle kalmaz, aynı zamanda hata yönetimini kolaylaştırır. Bu yaklaşım özellikle grafik ve büyük çaplı uygulamalarda, kaynak yönetimi için de oldukça önemlidir. Örnek uygulamamızda ise RAII’yi SDL kaynak sınıfları için kullanacağız. SDLResource sınıfını da tam olarak bu amaçla geliştirdik. Bu sayede;

  • Otomatik temizlik: Destructor’da kaynak otomatik olarak SDL API’leri kullanılarak serbest bırakılır,
  • Modern C++: Move semantics ile kopyalamanın önüne geçiyoruz,
  • Tip Güvenliği: Template ile derleme zamanında tip kontrolü yapabiliyoruz.

Olay (Event) Yönetimi (Observer Pattern’i Kullanılarak)

SDL kütüphanesi ve benzeri bir çok işletim sistemi ve pencere soyutlama kütüphanesi bir şekilde etkileşimleri size sunmak için gerekli API’leri sağlarlar. SDL kapsamında da benzer bir mekanizma mevcut.

SDL_PollEvent API’si ile sıradaki eventleri alabiliyorsunuz. uEngine4’te de hatırlarsanız, “Listener” uzantılı arayüz sınıfları ile alt sınıfları haberdar ediyorduk. Bu örneğimizde ise, “Observer” tasarım örüntüsünü kullanarak bunu yapacağız. Bu tasarım örüntüsü, Gang of Four’un en ünlü örüntülerinden birisidir.
Nedir peki?
Kısaca, bir nesnemizde olan değişikliklerin, bununla ilgilenen, diğer nesnelere otomatik olarak bildirilmesine dayanan davranışsal bir tasarım örüntüsüdür. Tahmin edebileceğiniz üzere grafiksel kullanıcı arayüzlerinde, kullanıcı tarafından tetiklenen olaylarda, bunlara yönelik gerekli aksiyonların alınması için de bu örüntü kullanılır. Bir diğer yaygın örneği de, QT framework’ü ile sunulan sinyal/slot mekanizmasıdır. Aşağıda bu örüntüye yönelik genel bir sınıf diyagramını görebilirsiniz. Burada, “observer”‘ların ilgilendikleri konudan notify API’si ile nasıl haberdar edildiklerini görebilirsiniz.

The Observer Pattern – MC++ BLOG

Biz de bu örüntüyü SDL olaylarının ilgili uygulama sınıflarına geçirilmesinde kullanacağız. Peki bu bize ne sağlıyor?

  • Düşük Bağımlılık (“Loose coupling”):  Olayı tetikleyen ile bunu tüketenler arasından bağımsızlık,
  • Genişleyebilirlik (“Extensibility”): Yeni dinleyiciler kolay eklenebilmekte,
  • Konuların Ayrılması (“Separation of concerns”): Farklı farklı mekanizmaların birbirlerinden ayrılabilmesi (UI, Girdi/Çıktı, vs). Bunların her bir farklı bir dinleyici olabilir.

SDL’e yönelik olayları dinlemek isteyen sınıflar (ki örneğimizde Sdl3Application sınıfı), EventObserver arayüzünü gerçekleyerek, olaylardan OnEvent API’si ile haberdar olabilmektedir. Dinlemek için de EventSubject sınıfının bir nesnesini uygulama sınıfı içerisinde oluşturup, sınıfın kendisini dinleyici olarak ekliyoruz.

Daha karmaşık uygulamalarda, sadece ilgili sınıfları da buraya dinleyici olarak ekleyebiliriz. İlaveten, uEngine4’teki gibi, olay tiplerine göre de özelleştirmeye gidilebilir. Örneği çok karmaşıklaştırmamak adına şu an bu şekilde ilerliyor olacağız. Sdl3Application sınıfı içerisindeki, OnEvent fonksiyonu içerisinde de, bu olayların tipine göre (event.type, SDL_EVENT_*), gerekli işlemlerin yapıldığını görebilirsiniz.

SDL_Event detayları için https://wiki.libsdl.org/SDL3/SDL_Event adresine göz atabilirsiniz.

Görsel Nesneler (Bileşen Örüntüsü) ve Bunların Oluşturulması (Fabrika Örüntüsü)

Bu başlığımızda ise, basit görsel bileşenleri nasıl oluşturacağımıza bakacağız. uEngine4 ve QT tarzı kütüphaneler bu amaçlar “Painter” dediğimiz ve temel çizim kabiliyetlerini içeren API’ler sunmaktadırlar. Oyunlarda ise son zamanlar, ECS (Entity Component System) denilen ve performansı da öne çıkaran yaklaşımlar yaygın olarak kullanılmakta. Biz ise örneğimizde, miras tabanlı bir bileşen yaklaşımı izleyeceğiz. Hemen örnek bir kod parçası ile başlayalım:

Burada her bir görsel nesnemiz için temsili olarka GraphicalObject sınıfımız bulunmakta, daha sonra bu nesnelerimize kullanımına göre Component arayüz sınıfından türetilen bileşenleri (Transform, Velocity ve RendererComponent) ekleyerek davranışını belirleyebiliyoruz. Daha önce de ifade ettiğim gibi burada miras tabanlı bir yaklaşım izliyoruz ve Nesne Yönelimli Yazılım geliştirme pratiklerini uyguluyoruz. Bu da temel uygulamalarınız için yeterli olacaktır.

Peki bu nesneleri nasıl oluşturabiliriz?

Bunun için de yine çok bilindik bir tasarım örüntüsü olan Fabrika Örüntüsünü kullanacağız. Bu örüntüyü özellikle nesne oluşturma işlemlerinin kontrolünü soyutlamak ve genişleyebilirliği yönetmek için kullanıyoruz. Burada temel olarak, ilgili sınıfı direk oluşturmak yerine, bir fabrika sınıfı marifeti ile ilgili sınıfları oluşturmaya dayanıyor. Burada da, modern C++ ile gelen akıllı işaretçileri ve yukarıda bahsettiğim, bileşen tabanlı yaklaşımı uygulayabiliriz. Hemen ilgili sınıfımıza göz atalım:

Yukarıdaki fabrika sınıfı ile dikdörtgen, daire ve üçgen şekiller oluşturabiliyoruz. İlgili nesneleri de Sdl3Application::Initialize metodu içerisinde oluşturuyoruz.

Fabrika örüntüsünün faydalarına bakacak olursak:

  • Enkapsülasyon (Encapsulation): Nesne oluşturma mantığı bir yerde,
  • Tutarlılık: Aynı tip nesneler hep aynı şekilde yaratılmakta,
  • Uyarlama: Örnekte böyle değil ama, farklı konfigürasyon dosyası ya da girdileri ile nesne oluşturma uyarlanabilir.

Sonraki Adımlar

Evet sevgili yazılımperver dostlarım. Bu yazımın sonuna geldik ama uygulamaya yönelik söyleyeceklerim henüz bitmedi. Bir sonraki yazımda, bu geometrik nesneleri nasıl görselleştireceğimize, uygulamanın nihai haline ve bir araya getirilmesine bakıyor olacağız. O zaman kadar kendinize iyi bakın ve repodaki uygulamayı incelemeyi ve çalıştırmayı unutmayalım.

Kaynaklar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.