[title = DERLEME BEKLEMEYE SON!]

Intro ya da demo hazrlarken, zellikle rnn sonlarna doru programcnn uygulad genel yordam, bitirmeye alt part izlemek, kpler yanl dnyorsa kodu deitirip derleyip bir daha izlemek, renkler olmad gibiyse kodu deitirip derleyip bir daha izlemek, gfx part olmas gerekenden daha erken girdiyse kodu deitirip bir daha derleyip yine izlemektir Ta ki rn mkemmelleene ya da daha byk bir ihtimalle compo deadline gelene kadar bu byle devam eder. Buradaki en sorunlu durum tahmin edeceiniz gibi eliniz kolunuz bal beklediiniz derleme srecidir. Dergide bir yerlerde olmas gereken seri retim yazsn okuyup oradaki bir ksm yntemleri uygulayanlar iin, ufak deiiklikler sebebiyle tekrar derleme olay tarihe karacaktr. Bu yaz deitirme-derleme-izleme srecini tercih eden dier okuyucularn hayatn olduka kolaylatrmasn umduum birka programlama ynteminden bahsedecek.

[b]Derleme Profili[/b]

Hedefimizi derleme sresini drmek olarak belirlediimize gre nce derleme sresine etki eden olaylarn analizini yapalm. Bu sayede derleme iini en yavalatan ii bulup onu hzlandrmak, mmknse ortadan kaldrmak istiyoruz.

Analize sondan balayalm. [b]Linking[/b] (birletirme) ilemi okunup derlenmi kaynak dosyalarnzdan hazrlanan obje dosyalarnn (.obj uzantl) iindeki kodlarn tm uygulamay oluturacak ekilde tek dosyada toplanmasdr. Bu ilem byk ounlukla obje kodun assembly koduna birebir evrilmesinden ibaret olduu iin tm derleme olaynn belki de en hzl aamasdr. Bu yzden bunu atlyoruz.

Bir nceki aama derleyicinizin okuyup belli i yaplara evirdii kaynak dosyasnn koda evrilmesi ilemidir. Ancak bu da C/C++ gibi ksmen alt dzey diller iin byk ounlukla birebir evirmedir. Bu da gerekten ok hzl geen bir aamadr.

Bir nceki ve ilk aama ise kaynak dosyanzn iindeki yazlarn ayrtrlmas ([b]parse[/b] edilmesi) ve obje koduna evrimin kolaylaaca basit yaplara haline getirilmesidir. Bu aama if, while vs. ile yazdnz dil yaplarnn zlmesi, girdiiniz deiken, fonksiyon isimlerinin kontrol edilmesi gibi gerek hayatta devlet memuru olarak bile yapmak istemeyeceiniz bir sr amele i barndrr. C/C++ dillerinin esneklii (serbest sayda boluk vs.) de gz nnde bulundurulursa, bu aamada tm derleme zamannn byk ksmnn harcand anlalabilir.

[b]Headers are Evil!
Hemen celallenmeyin, bir okuyun :)[/b]

Peki, bu sreyi azaltmak iin ne yapacaz? Kodlarmz daha m ksa tutalm? Gerek yok. ok ksa deiken ve fonksiyon isimleri mi kullanalm? Asla! Bunlara kesinlikle ne gerek var ne de bunlar yapmanz size hz kazandrr. Zaten biraz incelerseniz, kaynak kodunuzun ne kadar uzun olursa olsun bazlarnn hep ayn srede derlendiini greceksiniz. Derlerken alt alta sralanan dosya isimleri, hepsi birer birer geliyor, her birinde yaklak 3 saniyelik bir bekleme



Bu dosyalarn tepesine bakn, ne gryorsunuz? Tabi ki include satrlar. Eklenen header (balk) dosyalarn inceleyin. Onlarn eklediklerini inceleyin. Aralarnda gl.h, windows.h gibi [b]cicantik[/b] dosyalar greceksiniz. ster inann ister inanmayn ama derleme srenizin yarsndan fazlas bu gibi dosyalarn parse edilmesi ile geiyor. Hem de bunlar kullanan her kaynak dosyas iin birer kez! Derleyicinizin headerlarnn olduu dizine gidin ve windows.ha bakn. Belki tm projenizden ok daha uzun bir kod! yle deilse de dzinelerce baka header ekliyor olmal.

[b]Compiler Firewall Yntemi[/b]

Bu gibi header dosyalarnn ilenme srelerinin azaltlmas iin zaten ok bilindik yntemler mevcut. Bunlarn en temeli [b]compiler firewall[/b] ad verilen n derleme ([b]preprocessing[/b]) komutlar. Bunlarn amac ayn kaynak kodunu derlerken bir header dosyasnn birden ok kez ilenmemesi. Bunun iin, ilk ileme ile bir makro tanmlayp sadece bu makro tanml iken header ilemek bir yntem, #pragma once satr bir baka yntem.

Ama bunlar windows.h gibi dosyalarda zaten var (bendekinde ilk yntem kullanlyor). Bu ancak kendi headerlarnz iin kullanabileceiniz (ve kullanmanz gereken) ve ok ufak apta hzlandrmalar salayan bir yntem. Zaten sorunumuz bu headerlarn tm kaynak dosyalarnz iin tekrar tekrar ilenmesi idi.

Bir kaynak dosyasnn windows.hn yeniden ilememesinin tek bir yolu var: windows.h eklememesi! Ama nasl olur? Onu kullanarak bellek ynetimi yapyoruz, zamanlama yapyoruz, DirectX hatta ve hatta OpenGL kullanyoruz. Bir rpda nasl atarz? Bir rpda atamayacaz zaten. Bunun iin DirectX, OpenGL, WinAPI gibi dev ktphanelerin sadece kullanacanz ksmlarn ayrp bir bir wrap etmeniz gerekiyor.

[b]SubAPI Yntemi[/b]

OpenGL ile bir demo yaptnz ele alalm. Demonuzun her yerinde genlerden oluan objeler iziyor, etrafta izgiler uuturuyor, dikdrtgenler basarak resimler gsteriyorsunuz. Gayet iyi, gayet ho. Lakin OpenGL, Bzier erileri de hesaplayabiliyor, ufak apta grnt ileme de yapabiliyor, isterseniz ekranda izilen cisimlerin etelesini de tutuyor. ok daha kts OpenGL, Windows altnda windows.h ekliyor!

Her kaynak dosyanzda gl.h eklemek, her kaynak dosyas iin birka saniye beklemeniz demek. 30 kaynak dosyal bir projede, 30 kaynak dosyanzn da kulland ve gl.h aran bir header srekli deitirmenizin gerekmesi demek, yaknda ldracaksnz demek. Bu yzden OpenGL gibi dev bir ktphanenin tm proje apnda grlmemesi lazm.

Bunu engellemek iin OpenGLin kullandmz ksmlarn projenin kalan ksmna sunan ufak al bir API (application programming interface) hazrlayabilirsiniz. Bu API, rnein C++ta Renderer ad verilen bir namespace altndaki ya da Cde render neki ile balayan global fonksiyonlardan ibaret olabilir.

[b]
Renderer::DrawSprite2D /* ya da renderDrawSprite2D */
(
float left,
float right,
float top,
float bottom
) ;

Renderer::Translate /* ya da renderTranslate */
(
float dx,
float dy,
float dz
) ;
[/b]

Evet, glTranslatein on bin farkl versiyonunu da kullanmayacaksnz. Kullanacanz tek bir versiyonu (glTranslate3f) kullanan baka bir fonksiyon yazn. Her yere glBegin, glEnd koymak yerine, gen, dikdrtgen izen bir iki fonksiyon hazrlayn. Anahtar nokta u ki; bu fonksiyonlarn hepsini tek bir kaynak dosyasnda tanmlayn ve gl.h sadece buradan ekleyin. Artk demonun btn kaynak dosyalar iin derleme bekleme derdi kalmad.

Aslnda bu sayede derleme sresini azaltmann yannda nemli bir i daha baarm olduk. OpenGLi demonun kodlarndan byk lde soyutladk. leride DirectXe ya da software bir motora, hatta [b]textmode[/b] motora gemek isterseniz iinizin ne kadar kolay olacan bir dnn. Tm kodlar batan yazmak yerine sadece bu fonksiyonlarn bulunduu kaynak dosyas hedefiniz olacak. Ksa srede farkl bir izim motoruyla demo yapmaya balayabileceksiniz. Nestedtan Carete gei aynen byle olmutur. Hatta tecrbeli okuyucularn fark edecei gibi, eski motorun kodunu yeni motorda altrabilirsiniz, hem de Renderer.cpp haricinde hibir kayna derlemeden!

WinAPIyi kullanrken de benzer bir yntem izleyebilirsiniz. En rahat tm ktphaneyi evrelemek yerine; bellekle uraan, disk ilemleri ile uraan, pencere ilemleri ile uraan farkl farkl subAPIler hazrlamak. Bu ekilde kod ynetimi kolaylaacaktr. Tm projenin bellek ihtiyalarn karlayan tek bir ara yz hazrlamak, sk karlalan bellek sorunlarnn zlmesine de yardm edecektir (her yerde new ve delete kullanmayla kyaslandnda).

[b]Hepsi ok iyi, hepsi ok gzel anesolan. Ancak bu APIler bana yetmiyor. Deli gnlm snflar oluturmak, hiyerariler yaratmak istiyor. Lakin snf tanmlarn headerlarda yapyoruz ve sen de takdir edersin ki bunlarn kulland tipleri gizli de olsa header ekleyene gstermek icap eder. Var mdr bu derde de bir dermann ha evlat?[/b]

[b]Private Parts are Evil![/b]

ok gzel bir noktaya parmak bastnz. Global fonksiyonlar iin sorunumuz yok ama bir snfn darya gzkmeyen ksmlarnn tanmn bile headerda yapmak zorundayz; nk snf paralayp tanmlayamyoruz. Haliyle bu ksmlarn ihtiyac olan headerlar, snfn kullancs tarafndan ihtiya duyulmasa da eklenmek zorunda kalyor.
Sorunumuzu bir rnekle anlatalm. Zamanlama iin bir timer snf hazrlyoruz, bu snfa ait objeleri bir kronometre gibi balatacaz, durduracaz, aritmetik ilemlerde gnlmzce kullanacaz. Ama WinAPIye zg yksek hassaslkta zamanlayclar kullanmazsak da lrz. Mevzu yaklak yle:

[b]
class Timer
{
public:
	Timer() ;
	~Timer() ;

	void Start() ;
	void Stop()  ;

	double GetTime() const ;

private:
	bool counting_ ;

	LARGE_INTEGER frequency_ ;
	LARGE_INTEGER zero_      ;
} ;
[/b]

Timermz iin Windowsun yksek hassaslkl zamanlayclarn kullanacaz. Bunun iin ilemciye zg bir saatin frekansn (frequency_) ve bala dediimiz ann zamann (zero_) tutmamz gerekiyor. GetTime dediimizde de kronometre alyorsa (counting_) timermz o anki sreye bakp, balang sresini karp, frekans da kullanarak saniye cinsinden kronometre deerini dnyor. Kronometre durmusa da en son durulan uzakl zero_ya atm olmamza gvenip bu deeri veriyor.

(Not: Burada basit bir yap olacak, kronometre her zaman sfrdan balayacak. Olay kavradktan sonra pause ve hz ayar gibi zellikler ekleyebilirsiniz.)

Tabi bu timer kullanmak isteyenlerin LARGE_INTEGERn ne olduunu bilmeleri, bu yzden windows.h kullanmalar gerekiyor. Bir dier seenek de Timer.hn tepesinde bu ii kendimiz yaparak snf kullanclarn bu dertten kurtarmamz. Her iki durumda da istenmeyen headerlardan kurtulamyoruz.

zm yntemlerimiz neler olabilir?

LARGE_INTEGERn 64bitlik tamsay olduunu varsayp yerine favori 64bitlik tipimizi koymak ve gereken her yerde tip geii yapmak? Sakn! Asla dier tiplerin yaplar hakknda varsaymlarda bulunmayn. LARGE_INTEGERn ya da onu oluturan paralarn en ufak deiikliinde kendinizi milyonlarca satr dzeltmeye alrken buluyor olabilirsiniz.

Hassas saatler yerine standart ktphanenin clockunu kullanmak? Belki. Hassaslkta epey bir kaybnz olacak ama en azndan daha kolay port edilebilir bir kod elde edeceksiniz. Eksi olarak ise, kullanc istemeden ctime eklemi olacaksnz ve bu mantkla devam ederseniz platforma zg ok iyi zelliklerden mahrum kalacaksnz.
Benim istediim ise hibir header eklemeye gerek brakmayan, zerinde alt platformun en iyi zmn kullanan ve bunu yaparken kullancya herhangi bir ey yanstmayan bir yntem.

[b]Opak Pointer Yntemi[/b]

Sorunumuz LARGE_INTEGER kullanabilmek iin tanmna ihtiyacmz olmas idi. Bu yntem soruna ok basit yaklam sergiliyor. Tanma ihtiyacmz var nk Timer objeleri oluturabilmek iin Timer objelerinin boyunu bilmeliyiz. Timer objelerinin boyunu hesaplamak iin de LARGE_INTEGERlarn boyunu bilmeliyiz. Ama anahtar u ki, kullanc asla zero_ ya da frequency_yi kullanmayacak. Dolaysyla onun bunlarn boyunu baka bir amala bilmesine gerek yok. Timer bu elemanlarn boyunu kullancya gstermeden onlar nasl kullanabilir?

Tabi ki pointer ile.

Bunlar private alanda birer pointer ile tutsam, constructorda oluturup destructorda silsem sorunum kalmayacak.

[b]
class Timer
{
public:
	Timer() ;
	~Timer() ;

Timer(Timer const &timer) ;
	Timer &operator =(Timer const &timer) ;

	void Start() ;
	void Stop()  ;

	double GetTime() const ;

private:
	bool counting_ ;

	union LARGE_INTEGER ;
	LARGE_INTEGER *frequency_ ;
	LARGE_INTEGER *zero_      ;
} ;
[/b]

(Not: Dinamik bellek kullanmna getiimiz iin varsaylan kopyalama ilemleri (element element kopyalama) isteimizi karlamayacak. Bu yzden kendi kopyalama rutinlerimizi girmemiz gerekti.)

Timer snfn grp Timer objeleri oluturmak isteyen bir kaynak dosyasnn artk windows.h eklemesine gerek kalmad. nk gizli alandaki LARGE_INTEGERlar kullanmayacak ve boylarn bilmesine gerek yok. Ve nk btn pointerlar ayn boydadr! Tek yapmamz gereken bunun ne tr bir deiken olduunu nbildirim olarak derleyiciye vermek (union).

Aslnda daha rahat bir kullanm tm private ksm zel bir snf iine alp tek bir pointer kullanmak.

[b]
class Timer
{
public:
	Timer() ;
	~Timer() ;

Timer(Timer const &timer) ;
	Timer &operator =(Timer const &timer) ;

	void Start() ;
	void Stop()  ;

	double GetTime() const ;

private:
class Implementation ;
Implementation *pimpl_ ;
} ;
[/b]

Timer.cppyi hazrlarken private datalara bu pointer zerinden ulamamz gerekecek. Timer.cpp iinde nce bu yeni snf tanmlamamz gerekiyor.

[b]
#include <windows.h>
#include Timer.h

class Timer::Implementation
{
friend class Timer ;

private:
	bool counting_ ;

	LARGE_INTEGER frequency_ ;
	LARGE_INTEGER zero_      ;
} ;
[/b]

Tm datay private yapp sadece Timer snfna eriim vermek bu snfn normal kullanm iin olmadn, sadece Timern arka plandaki ilerini yrtmek iin gerektiini rahata anlatyor (ufak bir comment satrnn yerini tutamaz tabi).

Bu ynteme opak pointer dedik, nk kullanc bu pointern arkasnda neler dndn gremiyor, haliyle de bilemiyor. (Zaten bilmemeli?!)

Timer constructormzda bu implementasyon objemizi oluturup destructorda yok etmemiz gerekiyor.

[b]
Timer::Timer()
:
	pimpl_(new Implementation)
{
// Baslangicta durmus halde.
	pimpl_->counting_ = false ;

	// Islemci saati frekansini al.
	pimpl_->QueryPerformanceFrequency(&pimpl_->frequency_) ;
	// Baslangic degeri sifir.
	pimpl_->zero_.QuadPart = 0ll ;
}

Timer::~Timer()
{
	delete pimpl ;
}
[/b]

Kopyalama ilerimiz de gerekli ama gayet kolaylar.

[b]
Timer::Timer(Timer const &timer)
:
	pimpl_(new Implementation(*timer.pimpl_))
{}

Timer &Timer::operator =(Timer const &timer)
{
	*pimpl_ = *timer.pimpl_ ;
	return *this ;
}
[/b]

Kronometre ileri

[b]
Void Timer::Start()
{
if(!pimpl_->counting_)
{
		// Baslama saatini al.
		QueryPerformanceCounter(&pimpl_->zero_) ;

		pimpl_->counting_ = true ;
}

return ;
}

Void Timer::Stop()
{
if(pimpl_->counting_)
{
	// imdiki saati al.
	LARGE_INTEGER now ;
	QueryPerformanceCounter(&now) ;

		// Baslama saatinden cikar.
		pimpl_->zero_.QuadPart =
now.QuadPart - pimpl_->zero_.QuadPart ;

		pimpl_->counting_ = false ;
}

return ;
}
[/b]

Son olarak da saatimizin okunmas

[b]
double Timer::GetTime() const
{
	// Sayiyorsak
if(pimpl_->counting_)
{
	// Simdiki saati al.
	LARGE_INTEGER now ;
	QueryPerformanceCounter(&now) ;

		// Baslama saatinden cikar.
		now_.QuadPart =
now.QuadPart - pimpl_->zero_.QuadPart ;

		// Sreyi saniyeye cevirip don.
		return 
			double(now.QuadPart) /
			double(pimpl_->frequency_) ;
}
// Saymiyorsak
else
{
		// Sreyi saniyeye cevirip don.
		return 
			double(pimpl_->zero_.QuadPart) /
			double(pimpl_->frequency_) ;
}
}
[/b]

[b]Kapan[/b]

Grdmz derleme sresi azaltma yntemlerini zetleyelim

Compiler firewall ynteminde headerlarmzn bana zel satrlar ekleyip bir kaynak dosyas iin iki kez okunmamasn salyoruz. Her ne kadar ufak kazanlar salasa da alkanlk edinmemiz gereken bir yntem.

SubAPI ynteminde byk ktphanelerin sadece kullandmz ksmlarn sunan ufak ktphaneler hazrlyoruz. Bu sayede devasa headerlar sadece tek kaynak dosyas tarafndan grlm oluyor. Bu yntemle portabilitymizi de artrm oluyoruz. Ve ok daha iyisi, ayn ii yapan farkl alternatifler arasnda geiimiz ok rahat oluyor. rnein:

#ifdef OPENGL
/* Burada OpenGL kullanan SubAPI */
#endif

#ifdef DIRECTX9
/* Burada da DirectX kullanan */
#endif

Bu ayrm kodumuzun izim yapan her satrna da uygulayabilirdik. Ama bu kadar temiz olmayacakt tabi ki.

Opak Pointer (ya da [b]pimpl idiom[/b]) ile SubAPIlerde elde ettiimiz gc snflarda buluyorduk. Bu yntem derleme srelerini olduka drmesinin yannda, ara yz ve implementasyon ayrntlarn istisnasz ekilde birbirinden ayrd iin de projenin tamamnn ynetimini kolaylatracaktr.

rnek vermek gerekirse, ana ilemleri tamamen GPU zerinde alan bir matris ktphanesi hazrlamtm. Ama bu ktphaneyi kullanmak iin hibir grafik ktphanesi header eklemeye gerek yoktu!

Bir baka rnek olarak da u anda tam olarak API bamsz (OpenGL, DirectX) bir doku snf kullandm ve sonulardan ok memnun olduumu sylemeliyim. Tam olaraktan kastm ise, iki API arasndaki farklara bakmak iin gei yaptmda, sadece linkleme yaplyor olmas.

Datalara bir pointer ile ulamak ufak da olsa ileri yavalatacaktr ama asl yaplan ile (rendering) kyaslandnda bu eksiye devede kulak denebilir.

Bu yntemler zerinde kafa yormaktan, hatta uygulayp sonularn denemekten ekinmeyin. Sonular karsnda akna dnp benimle mutluluunuzu paylamak istediinizde, ben atma binmi baka tutoriallara doru yol alyor olacam.