2010. július 30., péntek

Struktúra vagy osztály?

Azon programozási nyelveknél, amelyeknél létezik struktúra (struct) és osztály (class) típus is, időről-időre talán felvetődik az a kellemes kérdés, hogy vajon adott helyzetben az egyiket, vagy inkább a másikat kellene-e használni? Itt most elsősorban olyan helyzetekre (feladatokra) gondoltam, amelyekhez egyébként bármelyik típus megfelelő lenne (mondjuk azért, mert mindkettőnek lehet adattagja, metódusa, konstruktora, blabla...), de valamilyen oknál fogva nekünk mégis le kell tennünk a voksunkat az egyik mellett. Izgalmas kérdés, vizsgáljuk meg hát a dolgot .NET környezetben.
Sok apró különbség van a struct és class között, de az apróságokat most hagyjuk másra. Az első komolyabb eltérés az, hogy a class öröklődhet (egyik a másikból ugyebár), míg a struct nem. Ez tehát eleve döntő szempont lehet akkor, ha egy hierarchiát, vagy objektum-orientált tulajdonságokkal rendelkező struktúrát kell felépítenünk, mivel így ehhez a feladathoz csakis a class jöhet szóba.
A másik alapvető különbség az, hogy míg a class referencia (reference), addig a struct érték (value) típus. Na, ez már valami! A .NET-ben a referencia típusok a heap-en foglalnak helyet, és a már felesleges példányok eltakarításával a szemétgyűjtő (garbage collector) van megbízva. A szemétgyűjtő nem fut (nem takarít) állandóan, csak akkor, ha úri kedve úgy tartja, vagy inkább... amikor eljött az ideje. Ezzel ellentétesen az érték típusok a stack-en (veremben) tárolódnak, és az élettartamuk addig tart, amíg az adott típus hatóköre tart (pl.: egy függvény elejétől a végéig). Amint megszűnik a hatóköre, azonnal eltűnik a stack-ről, és hmmm... legalábbis nem foglal fizikailag helyet tovább.
Érték típussal általában nem annyira költséges dolgozni, mint referencia típussal egészen addig, amíg becsomagolásra vagy kicsomagolásra nem kerül sor. A becsomagolás (boxing) az, amikor egy érték típust átalakítunk referencia típussá (pl.: egy int belekerül egy object-be), a kicsomagolás (unboxing) pedig ennek a fordítottja. Csomagoláskor tehát az érték típus (vagyis a struct) költségesebb, mint az osztály (vagyis a class), mert - hogy a jelen példára alapozzak - egyszerűen több munka van vele.
Általában elmondható még az is, hogy érdemesebb class-t használni akkor, ha annak a memóriafoglalása (footprint) 16 bájt alatti, illetve a belőle létrehozott példány állapota (tartalma) nem módosul. Szóval akkor most struktúra vagy osztály? A válasz, mint látjuk: attól függ.

Put About

***

Soci jelezte nekem, hogy ez az írásom pontosítást igényel. Örülök, hogy felhívta rá a figyelmemet, és ezennel szeretném egy az egyben közzé tenni az általa írt kiegészítés szövegét: "A value typeok vermen laknak, ha lokális változóként példányosítjuk őket, de a heapen, ha része egy ref típusnak. A lényeg, hogy az őt tároló konténernek közvetlen része, ami verem, ha lokális változó és heap, ha az egy ott lakó ref típus." Socinak pedig még egyszer hálásan köszönöm.

Tao Start 11 - Normálvektor és ColorMaterial

A számítógépes grafikában nagyon fontos szerepet töltenek be a normálvektorok. Az árnyalási feladatoknál, a látható felszínek szűrésénél, illetve a megvilágítási feladatoknál egyszerűen nélkülözhetetlenek. A fogalommal már az előző bejegyzések során megismerkedtünk, de azért nem árt még egyszer górcső alá venni a témát.

2010. július 27., kedd

Még tovább gyorsítja a grafikát az OpenGL 4.1

Alig négy hónappal az OpenGL 4.0 végleges változatának publikálását követően máris frissítés érkezett a keresztplatformos grafikus fejlesztést lehetővé tevő specifikációhoz. Az új OpenGL 4.1 legfontosabb fejlesztése a 64-bites lebegőpontos vertex shaderbemenetek támogatásának megjelenése, de a specifikáció részét képező OpenGL ES 2.0 kompatibilitási rétegnek köszönhetően a mobil és asztali környezetben egyaránt futó alkalmazások fejlesztése is egyszerűsödött.

2010. július 17., szombat

Tao Start 10 - Árnyalási alapok

Elérkeztünk egy nagyon fontos állomáshoz, ami az OpenGL-t és a számítógépes grafikát illeti. Ugyanis elkezdünk foglalkozni a megvilágítás (lighting) kérdésével, hogy végre tetszetős ábrákat varázsoljunk a képernyőre. Az OpenGL természetesen itt is a segítségünkre lesz. Már most meg kell jegyeznünk, hogy az árnyalás/megvilágítás és az árnyékolás között óriási különbség van. Árnyalásnál minden felületet (face-t) külön kell vizsgálni, tehát az egymás közti viszonyokat nem kell nézni, csak a fény és a poligon pillanatnyi állapota számít. Az árnyékolás egy sokkal összetettebb dolog, hisz ott elvileg a poligonok is árnyékolhatják egymást. Ezt az OpenGL alapból nem is támogatja, tehát, ha árnyékolni szeretnétek, akkor egyértelműen trükközni kell. De most egyelőre csak az árnyalással foglalkozzunk, vagyis pontosabban a megvilágítással.

2010. július 9., péntek

Mit tud majd a DirectX 11

Akármilyen géppel vagy konzollal rendelkezzünk is, elvárjuk, hogy évről évre szebb játékok lássanak napvilágot. Egyre divatosabban használjuk a "shader" és "szűrés" kifejezéseket, minden eltelt nappal kritikusabbá válunk, és csak reméljük, hogy lassan eljön a nap, amikor már meg se lehet különböztetni a valóságot a játéktól. De vajon ez mikor következik be?

Mielőtt még elmerülnénk a DirectX 11 mélységeiben, nem árt egy kis technikaóra, hogy könnyebben érthető legyen, miért is várunk olyan sokat a Microsoft legfrissebb üdvöskéjétől. A videokártyák grafikus processzorai (GPU-i) abban különböznek a számítógép agyának is nevezett processzoroktól (CPU-któl), hogy míg utóbbiakat már évek óta szigorú szabványok alapján kell felépíteni, addig előbbiekre mindez nem igaz. Akármilyen architektúrát alkalmazhatunk, a lehetőségek határa a csillagos ég. Ahhoz viszont, hogy mindez működjön, szükség van egy alkalmazási felületre (API-ra), ami bármilyen videokártyát képes felismerni, s mellyel kommunikálva annak meghajtó programja kihasználhatja az eszközben rejlő lehetőségeket. Ez az API tehát létfontosságú a gyors és mindenki számára elérhető 3D-s grafika kiszámításában. Jelenleg két uralkodó típus osztja meg egymással a piacot. Az egyik az OpenGL, melyet az egyszeri játékosok főként az id FPS-eiből ismerhetnek (az összes teljesen 3D-s id motor arra épült), a másik pedig a tortából jóval nagyobb szeletet kivágó DirectX.


Mivel utóbbi nem szorul különösebb bemutatásra, legyen elég annyi, hogy ez manapság a legelterjedtebb platform. A forradalmi 8.1 után (Az übergéniusz, John Carmack szerint se készült azóta innovatívabb felület.) megérkezett a 9, mely bár koránt sem volt akkora durranás, mégis átvette a korábban uralkodó 7 helyét a játékvilágban. A DirectX 10-re a Windows Vista megjelenéséig várnunk kellett, ám mikor végre kijött, nagyon megosztotta a közönséget. Kihasználni a mai napig nem tudta senki, holott lényege nem is a grafikai tupír, hanem a programozást leegyszerűsítendő felépítésében rejlett. Ennek ellenére még most is a DirectX 9 a vezető, s még egyetlen olyan játék sem látott napvilágot, mely kizárólag a DirectX 10-re épült volna.


A Microsoft látva a kvázi sikertelenségét, nem is oly rég bejelentette API-ja 11. generációját, ami a 10-hez hasonlóan most is egy új operációs rendszer keretében érkezik majd (Windows 7). De vajon jó lesz-e ez nekünk? Szükségünk van-e még egy kihasználatlan felületre, avagy komoly forradalom készül? Bemutatómban igyekszem közérthetően megismertetni a DirectX 11 legfontosabb tulajdonságait és újdonságait.

Szebb jövőt

A DirectX 10 legnagyobb buktatói pont az őt teremtő cég ostobaságaiból fakadtak. Csak és kizárólag Windows Vista operációs rendszer mellett volt elérhető, egyetlen játék sem használta ki, a driverek nem támogatták rendesen, és a tetejébe még a beígért Flight Simulator patch sem érkezett meg időben. Szerencsére az óriás tanult a hibáiból, így a DX 11 Vista alatt is elérhető lesz, jóval nagyobb táborra szert téve már rögtön a megjelenésekor. Mindez persze mit sem érne, ha nem lehetne rendesen programot írni rá, de a birtokunkban lévő eddigi információk és a novemberben elérhetővé tett DirectX SDK DX 11-et is tartalmazó elemei kellően meggyőzőek. A legnagyobb előnye a DX 11-nek a DX 10-zel szemben, hogy míg utóbbi egy abszolút új irányba terelt fejlesztés volt, addig a 11 nagyon sok elemében hasonlít a DX 10-hez.



Ahhoz, hogy ezt megértsük, tudnunk kell, hogy is működik egy átlagos API. Legfontosabb feladata természetesen nem más, mint megmondani a hardvernek, hogyan számolja ki a 3D-s világ szintén 3D-s elemeit és miként rajzolja ki őket a képernyőre. Ezt a folyamatot hívjuk pipeline-nak. A DirectX 10 sok forradalmi lépést tett a dolgok egyszerűsítése érdekében, s utódja mindezeket egy az egyben át fogja venni. Amiben mégis más lesz, az a vertex számítások után beiktatott három új tesszellációs lépcsőfok. A tesszellátor feladata a térgeometriai formák apró elemekre bontása, hogy valami bonyolultabb épülhessen fel belőlük, alapanyagot, azaz számítási kapacitást spórolva ezzel. Továbbá megjelenik még a Compute Shadernek nevezett, minden tekintetben forradalmi eljárás, mely lehetővé teszi a fejlesztők számára, hogy magát az API-t megkerülve sajtoljanak ki extra erőforrásokat a GPU-ból. Ennek részletezésébe most nem mennék bele, de mindez magyarra lefordítva annyit tesz, hogy sokkal kevesebb munkával jóval nagyobb teljesítmény érhető el a DirectX 11 használata közben, egyre közelebb hozva a valóságos grafika álmának megvalósulását.


Joggal tehetjük fel a kérdést, hogy miért is jó nekünk mindez, ha olyan 70-30%-os arányban még mindig a DirectX 10.1-et kapjuk meg új köntösben? A válasz egy üzletileg kétségkívül zseniális ötletben rejlik. A 11-es Microsoft API megjelenésével nem csak az új csúcskártyák tulajdonosai jutnak előnyökhöz, hanem a mostani, DX 10.1-es VGA-k felhasználói is! Köszönhetően a 11 operációs rendszerhez és driverekhez szorosan kötődő programozási hátterének, megfelelő támogatással a mostani kártyák teljesítménye is nő. A legszebb az egészben, hogy ez még a meghajtókat programozó csapatoktól sem kíván túl nagy megerőltetést, mert a két platform közti átjárásnak hála pofonegyszerű lesz 10-ről 11-re, vagy épp fordítva portolni. Aki tehát nemrégiben vette meg HD4870 X2-esét, nem kell elkeseredjen, mert jó ideig szórakozhat még vele, különösebb kompromisszumok nélkül is. Meg kell még jegyezni, hogy a DX 11 a 10-től eltérően az összes régebbi kártyával együttműködik majd.

Nem csak gyorsabb, sokkal szebb is!

Ugyan a több szálon futó (angolul csak multi-threaded) programozás lehetősége már évtizedek óta adott, a fejlesztők csak nemrégiben kerültek vele konkrét kapcsolatba, hála a többmagos processzoroknak. A Microsoft felismerte az ebben rejlő lehetőségeket, s most végre úgy tűnik, élnek is velük. A DX 11 nem csak a grafikus engine több szálra való installálását támogatja, de maga a játék belső motorja, forráskódja is profitálhat belőle. Mi több, ez működik majd DirectX 10-zel felszerelt, de DX 11-et telepített gépeken is, noha elképzelhető, hogy egyes esetekben a gyorsulás helyett lassulást eredményez. Manapság még nem nagyon létezik olyan motor, ami komolyan szükségessé tenné ezt a felhasználási módot, de a jövő megpróbáltatásai (CryEngine 3.0, vagy a már régóta készülő Unreal Engine 4.0) napról napra közelebb vannak, így nem árt a felkészülés.



Ha pedig már a jövő, akkor új shaderek. A Microsoft's High Level Shader Language (azaz HLSL) a DirectX 11-gyel az 5.0-ás verziószámhoz érkezik. Mivel a HLSL felépítése mindig emlékeztetett a C programnyelvére, már épp ideje volt az osztályok és interfészek beimplantálásának. Akik egy kicsit is ismerik a programozást, tudják, hogy milyen nehéz nagy adategységekkel dolgozni, ha nincs objektumorientált háttér. Ez a helyzet a mai játékok shaderigényeivel is. A kódsorok nagyok, újra és újra be kell illeszteni őket, ami lassítja a motort és elnyújtja a fejlesztés fázisait. Az új megoldásnak hála viszont minden egyes fontosabb shader leírható lesz egy osztályban, mely később bármikor meghívhatóvá válik a forrásból. Ezzel együttvéve a mutatókat is száműzik a nyelvből, így a memória sem kap felesleges terhelést.

Végszó

Ugyan sok dologról nem ejtettem szót, és lenne még bőven írnivaló a DirectX 11-ről, de szándékosan fogtam vissza magam. Egyrészről már így is picit túl technokrata lett a megközelítés, továbbá pedig úgyis csak az elméletek és elképzelések vizein eveznénk, mert ettől fogva az ismereteink már nem mind bizonyítottak. Nincs tehát jobb dolgunk, mint várni, hogy a Windows 7 elvileg jövő év eleji premierjekor az új API berobbanjon a köztudatba. Ami viszont mindenképp biztos, az a DX 11 innovációja. Ilyet már a 8.1 óta várunk, s ha csak nem rontanak el valamit nagyon a Microsoft fejlesztői, nem is fogunk csalódni. Csak jelenjen már meg a Crysis 2, hogy láthassak egy multi-threaded dzsungelt!

2010. július 7., szerda

Képszintézis lépései

Végül is akkor, hogyan alkotunk képet a képszintézis során? A kérdés, jogos. A válasz elméletben nagyon egyszerű. Van ugye nekünk egy modellterünk (benne a világ koordinátarendszer), mibe a képszintézis során egy általános helyzetű téglalapot helyezhetünk el. Ezután mögéje egy szemet vagy kamerát állítunk fel, és a világnak a szemből az ablakon keresztül látható részéről készítünk képet. Amit végül a képernyő nézetében jelenítünk meg.
Ehhez valahogy meg kell valósítani azokat a felületeket, amelyekről a fény a képre juthat. Erre az eszköz a térgeometria, ami során a tárgyakat geometriai objektumokkal fogjuk ábrázolni. A képszintézis során a program által kezelt alapobjektumok általában a geometria primitívek, mint például a poligon, gömb, fényforrás, stb. Manapság az elterjedt módszerek minden tárgyat háromszögekre (poligonokra) bontanak fel, mivel ez az a síkidom, ami szükségszerűen mindig konvex, vagyis alkotópontjai mindig egy síkban vannak. Ellenkező esetben ugyanis hibák és torzulások keletkezhetnek a képen. A háromszögünk meghatározásához kell három pont és az azokat összekötő élek, valamint egy normálvektor, ami a felületre merőleges, és megmutatja, hogy az objektum melyik irányból látható (lásd később). Erről egyelőre csak annyit, hogy gyorsasági okokból a 3D-s programok a tárgyakat mind üregesnek tekintik, és a felületüknek mindig csak az egyik, látható oldalát számítják. Még azt is elmondhatjuk, hogy kellő mennyiségű polinomból bármilyen görbe felület utánozható annyira, hogy a különbségek teljesen elvesznek. Gondoljunk csak bele, hogy egy 32 szöget milyen közelről kell megnéznünk, hogy ne kört lássunk belőle. Láthatjuk, hogy az emberi szem korlátait itt a magunk hasznára fordítottuk. Ezek a felületek, objektumok, amikről a fény a képre juthat, az általunk létrehozott virtuális világunkban foglalnak helyet. Ezért a képszintézis első lépése a virtuális világmodellnek a képszintézis program számára történő lefordítása. A világ-koordinátarendszerben rendelkezésre álló primitívek alapján, a képernyőn, azaz egy másik koordinátarendszerben kell képet készíteni. A koordinátarendszer váltáshoz szükségünk lesz a geometriai transzformációkra. 3D grafikában ez a transzformáció vetítést is tartalmaz, hiszen a modell 3 dimenziós, míg a kép mindig 2. Ezek után röviden átvesszük a képszintézis főbb részeit, természetesen később bővebben is tárgyaljuk azokat.

A képszintézis lépései

A távlat fogalma egyszerű: képzeld el, hogy a nézőpont nincs a képernyő felszínén, hanem bizonyos távolsággal mögötte helyezkedik el. Aztán konvertáld azt egy nézőpont-középpontú relatív koordinátarendszerré a következőképpen: a z koordináta a képernyő belseje felé nő. Most oszd el a koordinátáidat z-vel. Ennyi az egész. Persze nem kell az irányokat x, y és z-nek nevezned, ez a helyzethez illő leírástól függ. És persze el kell döntened, mi történjen a "képernyőből kifelé tartó irányú" dolgokkal - de ez már egy másik kérdés. Illetve még azt is el kell döntened, hány egységnyivel van a nézőpont a képernyő mögött (ezt kell hozzáadnod z-hez az osztás előtt). Sőt, ha talán még léptékezni is akarsz bizonyos dolgokat, hogy jobban nézzen ki az egész. Egyik sem túl nehezen megoldható. Ami viszont bosszantó, hogy nem akarsz osztást végezni minden a képernyőre rajzolandó pixelre. Ez például azt jelenti, hogy találkozhatsz olyan vonallal, amelynek mindkét vége a képernyőn kívül van, de bizonyos része látható. Ez viszont már az ütközések vizsgálatának problémája. Vagy még inkább a vágásé, ez csak egy előretekintés az arról szóló fejezetre. A háromdimenziós objektumok kétdimenziós képernyőre való rajzolása is fontos eleme a képszintézisnek. Sokféle módszer létezik ennek megoldására. Néhány megközelítés a látónégyszöget képezi le a helyszínre, minden pixelen keresztül sugarakat lövellve, majd a sugár által eltalált objektum szerinti színt kiválasztva. Más megközelítések a helyszínt képezik le a látónégyszögre, minden objektumot arra rárajzolva, és figyelembe véve melyik objektum melyik előtt helyezkedik el. A fenti leképezés tehát egyfajta levetítés, melyek közül a két legnépszerűbb a perspektív- és a paralell-projekció. A paralell-projekció elvégzéséhez például, csak el kell hagynod a z koordinátát és az objektumokat a látónégyszögre, kell vágnod. A perspektíva-projekció általános megközelítése szerint minden x és y értéket el kell osztanod z-vel, vagy annak valamilyen többszörösével. A képszintézis a modell azon részét fényképezi, amely a 2D ablakon belül, vagy a 3D ablak és a szem által definiált végtelen piramison belül van. Az ezeken kívül eső objektumokat, objektumrészleteket valamikor ki kell válogatni, és el kell hagyni. Ezt a folyamatot nevezzük vágásnak (clipping). A transzformációk több objektumot is vetíthetnek ugyanarra a képpontra. Ilyenkor el kell dönteni, hogy melyiket jelenítse meg, vagyis melyik takarja a többi objektumot. Ezt a lépést röviden takarásnak nevezzük. 3D grafikában nyilvánvalóan a szemhez közelebbi objektumot kell választani. Ha sikerült eldöntenünk, hogy egy képpontban melyik objektum látszik, akkor a képpontot ennek megfelelően ki kell színezni.
Először is, a tárgyaknak bizonyos felületi tulajdonságait kell meghatároznunk, például, hogy hogyan és mennyire veri vissza, illetve ereszti át a fényt, milyen színű és hasonlok. Ezek alapján lehet valamilyen módszerrel megállapítani, hogy milyen színű fény jut róla a kameránkba. Ugyanis a 3D grafikában a látható szín a térben fennálló fényviszonyoknak a függvénye, bár lehet egy szín is. Árnyalás a kép egyes pontjain keresztül a szembe jutó fény intenzitását határozza meg a hullámhossz függvényében. A hullámhosszfüggő intenzitás-eloszlás spektrumnak nevezzük. Ezen spektrum által keltett színérzetet kell a megjelenítőeszköz lehetőségeinek függvényében a lehető legpontosabban visszaadni, a spektrumot kell leképezni az eszköz által megjeleníthető hullámhosszokra és intenzitásokra (tone-mapping). Ezt az eljárást árnyalásnak nevezzük. Az árnyékoló algoritmusokra azért van elsősorban szükségünk, hogy egy tárgyat élethűen meg tudjunk jeleníteni. Ugyanis annál élethűbb egy tárgy minél több sokszögre tudjuk felbontani, ami viszont kezelhetetlen adatmennyiség. Itt jön be a csalás a dolgokba, az egyes poligonok felületet nem homogén színűnek ábrázoljuk, hanem egy árnyékoló algoritmussal módosítjuk. Így sokkal realisztikusabb képet kapunk. Azt próbáljuk kifejezni, hogy a természetben látható megvilágított felületek a fényforrástól, a felület térbeli helyzetétől, az anyag minőségétől, valamint a szemlélő helyzetétől függően különböző fényességűnek látszanak. Az árnyalt képet előállító algoritmusok, az előbb említett követelményeknek próbálnak eleget tenni. Természetesen minél élethűbb egy kép, annál hosszabb a kiszámításra fordított idő is.
A legtökéletesebb algoritmus a radiositys eljárás, ami teljesen valós fényszimuláción alapul. Hátrány az, hogy elméletileg végtelen számú visszaverődést kell kiszámítania. Ezek száma ugyan csökkenthető az élethűség rovására, de meg így is nagyon nagy a számításigénye. Sokszor nem is szükséges a grafikában, nagy erőforrásokat igénylő megjelenítést alkalmazni, például az oktatásban geometriai modellek bemutatására más algoritmus is kielégítő képet adhat. Viszont egy építész által megtervezett lakóház fotóminőségű képnek előállításra kiválóan használható, vagy gondoljuk a filmgyártásra. A ma használt legélethűbb módszer raytracing algoritmus, ami csak azokat a fénysugarakat követi végig, amelyek a kamerába jutnak (az árnyékokhoz is kellenek meg plusz fénysugarak), de ehhez fordítva kell haladnia a fény utján, a kamerából a tárgyakon át a fényforrásba elvégre máshogy nem tudható, hogy mely sugarak alkotják majd a képet. Ez az algoritmus is valós fizikai képleteket használ, de az ütközések száma itt is korlatozott egyébként két tükröződő tárgy között, a végtelenségig ugrálhatna a programunk. A raytracingnek igazából egyetlen nagy hátránya van, hogy még a mai nagyteljesítményű gépeken is időigényes a számítás. A nagy számításigény kiküszöbölésére számos alternatív eljárás született mára. Ezek közül a legegyszerűbb a Gouraud-árnyékolás (ő egy francia matematikus, aki kitalálta), ami főleg gyorsasága miatt kedvelt a játékokban. A lényege az, hogy egy egyszerű színátmenetet generál a poligon felületre, amit az átmenet irányának, valamint kezdő és végszínének gondos megválasztásával egész élethűre lehet hangolni. Ennél fejtettebb a Phong-algoritmus (vietnami matematikus). Ez mar a valós fényviszonyokat veszi figyelembe, és képes a felületi becsillanás (specular highlight) számítására is. A jelenség lényege az, hogy a fény a felület egy kis részéről nagyon erősen verődik vissza, nagyjából ott, ahol a fényforrás tükörképe lenne.
A grafikusok eszköztárak a fentieken kívül meg egy sor egyéb funkciót tartalmaznak általában. Füst, köd modellezése, a vetett árnyékok megjelenítése, a különféle, geometriai testekkel nem modellezhető tárgyak és jelenségek szimulálása, valamint a nem, vagy csak túl bonyolultan modellezhető felületi mintázatok megjelenítését szolgáló textúrázás. Számos példáját láthatjuk manapság a 3D-s játékokban is.

Felhasznált és ajánlott irodalom
Foley, van Dam, Feiner, and Hughes: "Számítógépes grafika: Alapelvek és gyakorlat" c.
Dr. Szirmay-Kalos László: Számítógépes grafika, 2001

Füzi János: Interaktív grafika, 1997

2010. július 2., péntek

Az Egyke (Singleton) tervezési minta

Egy nagyon jó kis cikket találtam a tervezési mintákkal kapcsolatban Nyisztor Károly tollából, amit C#-ban meg is valósítottam. A bejegyzés és a forrás itt olvasható:

Az Egyke (Singleton) tervezési minta

Az egyik legalapvetőbb és valószínűleg leggyakrabban használt minta az egyke minta. A neve is mutatja legfőbb jellemvonását: az adott osztályból egyetlen példány jön létre az alkalmazás futása során. Erre akkor van szükség, ha az adott objektumból felesleges, sőt zavaró lenne több példányt létrehozni; jó példa erre a hibanaplózó vagy teszem azt az XML-feldolgozó osztály.

A cél tehát egy olyan módszer bevezetése, amely garantálja, hogy mindössze egy objektumot lehet létrehozni az osztályból. Az első, szembeötlő nehézség az, hogy az osztály konstruktorát bárki meg tudja hívni - ezt kell megakadályozzuk. A megoldás a konstruktor elrejtése - tegyük priváttá, és ezzel megakadályozzuk a közvetlen példányosítást. Azonban valahogy biztosítanunk kell az osztály példányosítását, hiszen különben mindössze egy használhatatlan osztályt kaptunk.
Ezt a célt szolgálja az Instance() tagfüggvény, amely az egyetlen osztálypéldány létrehozásáért felelős. Az osztály egyetlen példánya az első Instance() hívás során jön létre, az ezt követő hívások pedig ezt a példányt adják vissza. Egy Singleton osztály (avagy Egyke) létrehozásának menete:
  • rejtsük el a ctor-okat, a másoló ctor-t és az "operator =" -t (ezáltal lehetetlenné tesszük a közvetlen példányosítást)
  • deklaráljuk az adattaghoz történő hozzáférését biztosító, static public metódust (általában Instance() névre hallgat)
  • deklaráljuk az osztály példánymutatóját statikus privát adattagként (ez az adott osztályra egyetlen példányának címe)
  • gondoskodjunk arról, hogy az első hozzáféréskor létrehozzuk az egyetlen osztálypéldányt, minden azt követő híváskor pedig a már létező objektumpoinetrt adjuk vissza
Kérdés

Miért statikus az Instance() metódus? Hamarosan jön a kézenfekvő magyarázat, de előbb vizsgáljuk meg a kódot:
   using System;

public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();

private Singleton() { }

public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
Az előző kérdésre - azaz, hogy miért static az Instance() tagfüggvény? - a fenti kódrészletben található a válasz: az osztálynak eredetileg nincs érvényes példánya, és érvényes példánymutató nélkül pedig nem tudunk meghívni nem-statikus tagfüggvényeket. A statikus függvények kivételt képeznek, hiszen az osztálypéldány megléte nem előfeltétel. (A statikus függvények korlátja, hogy kizárólag statikus adattagokon dolgozhatnak.)
Az egyik felmerülő gond a fenti Singleton megvalósítással a konkurens hozzáférés. Ha több szálból (nagyjából) egyszerre hívják az Instance() függvényt, előfordulhat, hogy az if(Instance == null) ellenőrzést egyszerre hajtják végre, majd több példány is létrejön, hiszen a feltétel egyik szálnál sem teljesült. Erre természetesen vannak megfelelő megoldások és nyelvi elemek, de a többszálúság rejtelmeibe egyelőre nem szeretnék jobban belemenni...

Eddig tartott Nyisztor Károly remek bejegyzése. Természetesen a többszálúsággal kapcsolatos problémát a fenti kód már orvosolja. Ezzel kapcsolatban további információkat az alábbi linken találhatunk: msdn
További jó kódolást mindenkinek!

2010. július 1., csütörtök

Tervezési minták

"Objektumközpontú programot tervezni nehéz, újrahasznosíthatót még nehezebb. Meg kell találni a megfelelő osztályokat és az osztályok közötti kapcsolatokat. A tervnek igazodnia kell a megoldandó problémához, de eléggé általánosnak kell lennie ahhoz, hogy később könnyen módosítható legyen. A tapasztalt tervezők jó terveket készítenek, a kezdők viszont elvesznek a lehetőségek dzsungelében. Éppen ezért jó lenne, ha a tapasztalt tervezők-fejlesztők valamilyen módon át tudnák adni a kezdőknek a jól működő megoldásokat. Ha egy problémára ismerünk egy megoldási mintát, ami már bevált, akkor azt legközelebb, hasonló problémánál is alkalmazhatjuk. Ezt felismerve, a GoF (Gang of Four – „Négyek bandája”: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) összegyűjtötte a tapasztalt objektum-orientált tervezők mintáit (23 darabot) és egységes leírást, magyarázatot készítettek hozzájuk. Így egy katalógust kaptunk, amelyben programtervezési minták találhatók. Az ő definíciójuk szerint a minta: „Egymással együttműködő objektumok és osztályok leírása, amely testreszabott formában valamilyen általános tervezési problémát old meg egy bizonyos összefüggésben.” A tervezési minta azonosítja a részt vevő osztályokat és objektumokat, szerepüket és kapcsolataikat."
Szabolcsi Judit: Szoftvertechnológia, 2010

A fenti bevezető szépen összefoglalta mindazt, amiről én is írni akartam. A magyar nyelvű C#-os könyvek alig-alig foglalkoznak a tervezési mintákkal, csak nyelvi elemek és a .NET környezet bemutatására szorítkoznak. Pedig már egy kezdő programozó is viszonylag hamar beleütközhet olyan problémákba, ahol nagy szüksége lenne egy tapasztalt fejlesztő tanácsaira. Ebben segítnek a tervezési minták, hiszen azok kiállták a gyakorlat próbáját. Ezért érdemes megismernünk őket akkor is, ha nem vagyunk éles fejlesztők, hiszen sok időt nyerhetünk velük…