Merhaba sevgili yazılımperver dostlarım. Bugünkü yazımda, uEngine4 içerisinde sessiz sessiz duran bir proje hakkında yazıyor olacağım: “time”. İsminden de anlaşılacağı üzere, bu proje içerisinde, projelerinizde ihtiyaç duyabileceğini bir çok zamanlayıcı, ölçüm ve benzeri araçları bulabilirsiniz. Mevcut uEngine içerisinde de, bu sınıfları kullanıyor olacağım. Ayrıca, bunların bir kısmına çeşitli yazılarımda da yer vermiştim. Yazıların bağlantılarını da ekliyor olacağım.
Uzun bir süre önce, zamanlayıcılar ve benzeri araçların bir çoğu için platformlara (işletim sistemleri) özgü kütüphane ve yaklaşımları kullanmak zorundaydık. C++ 11 ile artık bunların bir çoğunu, standart olarak sunulan API’leri kullanarak gerçekleştirebiliyoruz. Elbette bir adım öteye gitmek istediğimiz zaman platforma özgü bir takım kütüphane ya da API’leri kullanmamız gereken durumlar oluyor ama çoğunlukla standart araçlar işiniz görecektir. Aşağıya C++ 11 ile sunulan std::chrono yazılarımı ekliyorum, devam etmeden göz atmanızda fayda olabilir:
- https://www.yazilimperver.com/index.php/2021/07/23/haftalik-c-39-stdchrono-1/
- https://www.yazilimperver.com/index.php/2021/08/16/haftalik-c-39-stdchrono-2-clocks-time_point/
Bu sınıflara ilişkin kaynak kodlara aşağıdaki adresten ulaşabilirsiniz:
O zaman daha fazla vakit kaybetmeden sınıflara göz atalım.
Timestamp
Bu sınıf, sizlere anlık zamana ilişkin çeşitli bilgileri sunan statik API’lerden oluşmaktadır.
Ayrıca ilgili yazıya da https://www.yazilimperver.com/index.php/2020/11/09/haftalik-c-33-zaman-bilgisi-etiketi/ adresinden ulaşabilirsiniz.
Temel olarak sunulan API’ler ve işlevleri şu şekilde:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Basit bir şekilde Epoch anindan bu yana geçen zamanı milisaniye cinsinden almak için static uint64_t getTimestampInMsec(); // Asagidaki fonksiyon mevcut zamanı (Tue Sep 27 14:21:13 2011) formatında doner static std::string getTimeString(); // Bu fonksiyon, mevcut zaman bilgisini biraz daha sayısal ve kayıtlar icin uygun bir formatta sunuyor bizlere // Format şu şekilde "2020-11-09 20:19:45.217" static std::string getDetailedTimeString(); // Bu fonksiyon bir önceki metodun aynısını verilen zaman için yapıyor. Özellikle çeşitli kanallar aracılığı // ile edindiğiniz zaman bilgisini görselleştirmek için faydalı olabilir static std::string getDetailedTimeString(uint64_t msec); |
ChronoUtil
Bir önceki sınıfa benzer şekilde, C++ 11 ile sunulan üç temel zamanlayıcı kavramı olan süre, saat ve zaman noktası kabiliyetlerini kullanarak, zamana ilişkin çeşitli bilgilerin sunulması amacı ile kullanılabilecek sınıftır (sınıfa ve ilgili kavramlar için https://www.yazilimperver.com/index.php/2021/08/16/haftalik-c-39-stdchrono-2-clocks-time_point/ yazıma göz atabilirsiniz).
Sınıf ile sunulan API’ler ise aşağıdaki gibidir:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// timepoint nesnelerimizi time_t tipine, onu da takvimsel saat gösterimine (ör. Mon May 23 13:44:00 2011), çevirmek için kullanacağımız fonksiyondurdur static std::string ToString (const std::chrono::system_clock::time_point& tp); // Takvimsel zaman bilgilerinden, sistem saatinin time_point nesnesine çevirmek için kullanabileceğimi fonksiyondur static std::chrono::system_clock::time_point MakeTimePoint (int32_t year, int32_t mon, int32_t day, int32_t hour, int32_t min, int32_t sec = 0); // timepoint nesnelerimizi time_t tipine, onu da takvimsel saat gösterimine (ör. Mon May 23 13:44:00 2011), çevirmek için kullanacağımız fonksiyondurdur static std::string ToString (const std::chrono::system_clock::time_point& tp); // Uc tip saat tipinin sonuclarini gosterir static void PrintThreeClockTypeInfo(); // Sistem saatine iliskin bilgileri basalim static void PrintSystemClockInfo(); // Saati şablon parametresi olarak geçirelim template <typename T> static void PrintClockInfo(){ std::cout << " precision: " << T::num << "/" << T::den << " second " << "\n"; typedef typename std::ratio_multiply<T,std::kilo>::type MillSec; typedef typename std::ratio_multiply<T,std::mega>::type MicroSec; std::cout << std::fixed; std::cout << " " << static_cast<double>(MillSec::num)/MillSec::den << " milliseconds " << "\n"; std::cout << " " << static_cast<double>(MicroSec::num)/MicroSec::den << " microseconds " << "\n"; } |
StopWatch
Bu sınıf ise, uygulamalarınız içerisinde ihtiyaç duyacağınız ölçüm işleri için kullanılabilecek API’leri sunmaktadır. Bunu da yaparken, C++ 11 ile sunulan std::chrono kütüphanesini kullanır. Sunulan API’ler şu şekildedir:
1 2 3 4 5 6 7 8 9 10 11 12 |
/* @brief Referans zamanindan bu yana gecen mikrosaniyeyi doner. */ uint64_t ElapsedUs() const; /* @brief Referans zamanindan bu yana gecen milisaniyeyi doner. */ uint64_t ElapsedMs() const; /* @brief Referans zamanindan bu yana gecen saniyeyi doner. */ uint64_t ElapsedSec() const; /* @brief Sayacimiz sifirlar * @return Guncellenen referans zamanini (o anı) donelim */ ClockToUse::time_point Restart(); |
SleepUtil
Bu sınıf Windows ve diğer sistemler için hassas bekleme API’leri sunan bir sınıftır. Temelde iki API sunmaktadır.
1 2 3 4 5 |
// Mikrosaniye seviyesinde windows uygulamalari icin bekleme sunar static void USleep(int64_t sleepInUSec); // Platform bagimsiz hassas bekleme kabiliyeti sunar static void PreciseSleep(double seconds); |
bunlardan ilki Windows API’lerini kullanarak, hassas bekleme işlevi sizlere sunar. Bunu da yaparken 100 nanosaniye seviyelerinde girdileri destekler ve SetWaitableTimer API’si kullanılır (https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setwaitabletimer). Bu API sizlere milisaniye ve 100 nanosaniye çözünürlüklerde, haber veren zamanlayıcılar ayarlamanıza olanak sağlar.
İkinci API ise, sizlere platformunuzdan bağımsız olarak “Welford” algoritmasını kullanarak bir bekleme fonksiyonalitesi sunar.
Bu algoritma temelde işlemci kullanımı ile zamanlayıcı hassasiyetini dengeler. Detaylar için https://www.wikiwand.com/en/Algorithms_for_calculating_variance#/Welford’s_online_algorithm‘na göz atabilirsiniz.
FPSTimer
Bu sınıf, özellikle oyun ve grafiksel uygulamalarda, saniyede çizilen sahnelerin miktarını kontrol etmek için kullanmanıza olanak sağlayan bir sınıftır. Detaylar için https://www.yazilimperver.com/index.php/2017/12/09/oyun-donguleri/ yazıma göz atabilirsiniz. SdlApplication içerisinde de kullanımını görebilirsiniz.
ThreadedTimer
Evet, zamana bilgilerini görüntüleme, bekletme ve ölçmeye ilişkin servisleri sunan sınıflara göz attıktan sonra, sıra geldi gerçekten zamanlayıcı kabiliyetini sunan bir sınıfa göz atmakta. Peki, ne kast ediyorum? Basitçe, belirli bir süre sonra beni haberdar eden ya da vereceğim bir sürede bir beni haberdar edecek basit ve modern bir araç.
Öncelikle bunu yapmanıza olanak sağlayacak, platform bağımlı API’ler bulunmak ile birlikte, bu sınıfta aslında, temel C++ sınıflarını kullanarak bunu nasıl yapabileceğimiz göreceksiniz ki bu da aslında ayrı bir thread içerisinden bunu takip etmek olacak (not: bunu milyon tane farklı yöntemi olabileceğinin farkındayım, bu sınıfın onlardan birisi olduğunu kabul edebilirsiniz, diğerleri için https://letmegooglethat.com/?q=c%2B%2B+timer+example)
Bu sınıf ile sunulan API’ye aşağıdan ulaşabilirsiniz:
1 2 3 4 5 6 7 8 9 |
//! @brief Milisaniye cinsinden verilen süre geçince function'ı çağıracak olan zamanlayıcı API'sidir void OneShot(auto function, int32_t delayInMsec); //! @brief Milisaniye cinsinden verilen sürede bir function'ı çağıracak olan zamanlayıcı API'sidir void Periodic(auto function, int32_t intervalInMsec); //! @brief Zamanlayıcı threadini sonlandırmak için kullanılacak olan API'dir void Stop(); |
Burada ilk aklınıza takılan function kullanımı olabilir. Bunun yerine, bu sınıfa ya da API’lere IListener tarzında bir arayüz geçirerek de, ilgili sınıfı haberdar edebilirsiniz. Ben ise bu halini kullanacağım.
StoppableTimer
Evet, yazımda değineceğim son zamanlayıcı sınıf bu olacak. İsminden de anlayacağınız üzere, durdurulabilir bir zamanlayıcı kabiliyeti sunan bir sınıf. Bu noktada, bir dakika yazılımperver, bir önceki sınıf zaten bunu sunmuyor muydu? diye sorabilirsiniz. Dikkatli okuyucularım, ilgili sınıfın bunu sunmadığını görecekler. Tek atımlık zamanlayıcı ve periyodik API’ler için zamanın dolmasını beklemek zorundasınız, neden peki? Çünkü bizim takip ettiğimi yaklaşım için (uyutmak) bu mümkün olmuyor, fakat platforma özgü API’ler için bu mümkün olabilir. Peki bunu nasıl kotarabiliriz?
Yardım çok uzakta değil, “condition variables” 🙂 Evet, condition variables ile sunulan ve belirli bir süre beklemeye olanak sağlayan wait_for API’si bizim için biçilmez bir kaftan (bu konuya ilişkin yazım için ise bağlantısına göz atabilirsiniz).
Bu API hem bekleme işlevini sunuyor hem de durdurmak istediğimizde, geçirdiğimiz predicate sayesinde beklemeyi de sonlandırmaya olanak sağlıyor. Aşağıya ilgili API’nin nasıl kullanıldığını görebilirsiniz:
1 2 3 4 5 6 7 8 |
mThread = std::thread([=]() { auto lock = std::unique_lock<std::mutex>(mMutex); mCV.wait_for(lock, duration, [this]() { return mShouldStop.load(); }); if (!mShouldStop) callback(); }); |
durdurmak için de:
1 |
mCV.notify_one(); |
API’sini çağırmanız yeterli.
Bu sınıf içerisinde periyodik API’yi eklemedim ama isterseniz bunu eklemek bir önceki sınıfa bakarak zor olmayacaktır.
Evet sevgili dostlar, bu yazımda, uEngine4 ile sunulan zamanlayıcı ile ilgili sınıflara bir bakış atmış olduk. Bundan sonra, bir yazımı da, boost tarafından sunulan zamanlayıcılara ayırmayı planlıyorum. O zamana kadar bol kodlu günler 🙂