Épp "rajzolgatós progit" csinálok. Az elvárás valami olyasmi, a felhasználó ne csak egyszerűen rajzparancsokat tudjon definiálni, hanem ún. layereket is. Egy layer (vagy sprite) egy "vászon a vászonon", vagyis egy olyan logikai egység a képen, ami egybe tud fogni több rajzolási parancsot, és utána ezekre egységesen lehet hivatkozni (pl. ha definiálod a "nyuszi" layert, amire kirajzolsz egy csomó kört meg vonalat, amiből kijön a nyuszi, akkor később, amikor ebbe bele akarsz zoomolni, vagy odébb akarod taszigálni a vásznon, nem kell minden kört és vonalat újradefiniálni, elég lesz csak a "nyuszi" layert odébbtolni).

Az első ötletem az volt, hogy lesz egy Shape nevű absztrakt ős-osztályom, aminek lesz egy absztrakt paint nevű metódusa. Ennek lesznek gyermek-osztályai (line, arc stb.), akik implementálják a paint metódust. Amikor a Layer osztály kirajzolja magát, akkor pusztán annyit kell tenni, hogy végigiterál azokon a Shape-származékokon, amik hozzá lettek regisztrálva, és mindegyiknek meghívja a paint metódusát. Mivel a felhasználás során várhatóan egy csomó transzformációnak lesz kitéve maga a Layer, ami az egyes Shape-eket meghatározó koordináták újraszámolását igényli, ezért az első ötletem az volt, hogy a koordinátákat nem az egyes Shape-gyermekek példányai fogják tárolni, hanem magának a Layer-nek lesz egy változója, ami egy egybefüggő memóriaterületen eltárolja az összes rajzolóparancs összes pontját, így a geometriai transzformációknál elég lesz ezen a memóriaterületen végigiterálni, ami tippem szerint gyorsabb, mint egyenként végigugrálni az egyes Shape-eken.

Amikor egy bizonyos technikai részletkérdéssel felmentem a stackoverflow.com-ra és elmeséltem a problémát (http://stackoverflow.com/questions/10915568/is-it-possible-to-declare-a-virtual-static-constant-value-in-a-c-class) az egyik commentemben, azt a választ kaptam, hogy ez így hibás design és javasolták a Head First Design Patterns című könyv tanulmányozását (amire most se pénzem, se időm nincs, sajnos, pedig tényleg érdekesnek hangzik).

Azóta gondolkodom rajta, hogy vajon miért olyan hibás ez a design, illetve, hogy mit kéne benne máshogy csinálni, hogy egy hozzáértő is azt mondja rá, hogy jó a design.

Van bármi ötletetek?

Utoljára módosította SAdam 2012.VI.08 11:22-n
Bejegyzés módosítása | PermaLink
Votes disabled.

Hozzászólások (13)

UPi hozzászólása 2012-06-08 11:41-kor

Gondolatok:

  • Egy "hosszú" iterálás tényleg lehet gyorsabb, mint rövid iterálások iterálása, DE: ez nekem a "premature optimization" kategóriájába esőnek tűnik. A helyes eljárás az, hogy először csinálsz egy "naív" implementációt, és ha kiderül, hogy nagyon lassú, akkor utána kiméred, hogy hol lassú, és ott informált optimalizálást végzel.
  • A static és a virtual egymásnak antitézisei, szóval a kérdésfeltevésed arra utal, hogy nem érted, hogy valójában ezek a fogalmak mit takarnak. (Analóg kérdés, hogy hogyan jelöljük kottában a szünetjel hangmagasságát.)
  • Nem írtad le, hogy mi a célja a munkálkodásodnak. Ez valami házi feladat, vagy tényleg egy cél amit el akarsz érni? Ha az utóbbi, akkor éppen újra feltalálod az SVG-t, ami rossz ötlet.

A megoldás arra, hogy egy Shape egy másik Shape referencia pozíciójához képest rajzolja ki magát az lenne, ha vagy deklarálsz egy globális transzformációs mátrixot, vagy a paint metódus paramétereként adsz át egyet (ez a tisztább megoldás). A paintet pedig úgy implementálod, hogy a "logikai" koordinátákat ezzel a transzformációs mátrix-szal szorozza fel. Amikor egy összetett Shape felhívja a gyermek Shape-jeinek paint-jét a megfelelően módosított transzformációs mátrix paraméterrel.

Az előnye ennek továbbá az, hogy a layereket tudod elforgatni, kicsinyíteni-nagyítani, stb.


SAdam hozzászólása 2012-06-08 12:20-kor

(Válaszképp erre)

Azt tudom, hogy a static és a virtual kizárja egymást. Azért hivatkoztam erre, mert amire tulajdonképp szükségem lenne, az egy olyan deklaráció, aminek van virtual és static aspektusa is, a következő módon: lehessen definiálni egy olyan konstanst (nem függvényt, egyszerű adat-konstanst), ami egyrészt garantáltan jelen van minden egyes gyermeknél, de a konstans értékét minden gyermeknél külön-külön meg tudnám határozni (ez a "virtual" aspektus), másrészt (konstansról lévén szó) nem kellene minden egyes példánynak definiálnia az értéket, hanem az értéke rendelkezésre állna már az osztály példányosítása előtt, pontosabban, a példányoktól teljesen függetlenül (ez a "static" aspektus).

Sajnos már kinőttem a házi feladatok korából. Arról van szó, hogy az egyik projekt, amin dolgozom, egy olyan környezetben fut, ahol a built-in canvas egyszerűen túl lassú, és ezért saját canvas-implementációt kell csinálnunk. Egy bizonyos Cairo API az, amit használnom kell (pontosabban, nem a Cairo API-t, hanem, amit az általunk használt környezet SDK-ja ebből láthatóvá tesz a számunkra), ez egyébként érti az SVG bemenetet is, és az egyik első ötlet pont az volt, hogy rakjunk össze egy SVG-szövegfile-t, és ezt küldjük be a Cairo API-be, rábízva a renderelést. A bajom ezzel annyi (bár tény, hogy nem teszteltem le), hogy nem hiszem, hogy valóban gyorsabb lenne egy SVG adathalmazt editálgatni és újra-renderelni másodpercenként 24-szer, mint a Cairo-ban elérhető parancsokat használni a rajzoláshoz.

Amit a transzformációnál írsz, azzal az az egyetlen bajom, hogy ez esetben minden egyes renderelésnél (ami, ha a felhasználó épp változtat valamit a képernyőn, akkor durván másodpercenként 24-szer várható) egy csomó extra szorzás-összeadás az, hogy újraszámolom a koordinátákat. Ehelyett a terv az, hogy a pontokat úgy ábrázolom, hogy egyrészt eltárolom a "logikai" koordinátáikat, másrészt elteszem a "fizikai" koordinátákat is, és ezeket csak akkor számolom újra, ha a felhasználó épp azt a Layer-t változtatja át, amiben az adott koordináta előfordul. Transzlációs mozgásoknál szerencsére nem kell semmit sem csinálnom, mert a Cairo maga megoldja, hogy ha egy Layert áthelyezek, akkor ahhoz elég csak a Layer origójának a változását megadni, a többit már kezeli.


Ulmar hozzászólása 2012-06-08 17:34-kor

A konstanssal mit szeretnel kifejezni? Tipusmeghatarozasra akarod hasznalni?


SAdam hozzászólása 2012-06-08 18:11-kor

(Válaszképp erre)

A konstans ebben az esetben azt tartalmazná, hogy az adott rajzolási parancs (ami minden esetben az absztrakt Shape osztálynak egy leszármazottja lenne) pontosan hány paramétert igényel. Vagyis pl. a LineTo osztálynál a konstans értéke 2 (x és y), a Circle osztálynál 3 (az x, y középpont és az r sugár), stb.


SAdam hozzászólása 2012-06-08 18:15-kor

Egyébként azóta találtam egy olyan megoldást, amivel kikerültem a problémát (írtam egy külön memória-kezelő osztályt, ami regisztrálja, hogy melyik rajzolóparancs mekkora memóriablokkot igényelt és így egyszerű pointer-aritmetikával meg lehet határozni, hogy melyik rajzoló-parancs hány paramétert igényelt). Persze végignézve a kódon, amit írtam, egyre inkább látom, hogy miért mondhatta az illető látatlanban, hogy nem a legszerencsésebb megoldást választottam...


UPi hozzászólása 2012-06-09 09:33-kor

Továbbra is tartom azt a korábbi felvetésemet, hogy ha rajzolási műveletenként néhány szorzást akarsz megspórolni, az korai optimalizálás. Az eredmények cache-elésére képes mátrix osztályt írni egyébként nem olyan bonyolult, de megintcsak, akkor érdemes ilyesmibe vágni a fejszédet, ha méréssel alá tudod támasztani, hogy ott totojázza el az idejét a rendszer.

Egyébként lehet, hogy ami neked kell, az az OpenGL? Elvben jól csinálja azokat a műveleteket, amiket leírtál a kör kivételével, amit poligonnal kell közelítened. A VacuumMagic pl OpenGL-lel megy, és egy csomó izét rajzol másodpercenként százszor.


SAdam hozzászólása 2012-06-09 09:53-kor

(Válaszképp erre)

Ja, OpenGL is szóba jött, de a bonyolultsága miatt egyelőre megpróbálom megoldani natív API hívásokkal. Ha nem lesz elég gyors, akkor jöhet az OpenGL, igazából már szereztem is egy példakódot, hogy azt hogy lehet abba a környezetbe integrálni, amiben dolgozom. De maga az integrálás elég gusztustalan és valószínűleg a későbbiekben csak még bonyolultabb lesz a support, ezért ez csak nagyon végső megoldás.

Egyébként néha én is túlzásnak érzem, amit művelek (mármint azt a részét, hogy előre optimalizálok egy csomó dolgot, ami egyáltalán nem biztos, hogy annyit számít). Az a frusztráló, hogy amit a progink csinál (kottát jelenít meg), azt elvileg az általunk használt környezet saját canvas-objektjének simán vinnie kéne, mindenféle szaggatás nélkül...


UPi hozzászólása 2012-06-09 09:58-kor

Lehet, hogy most hülyeséget kérdezek, de egy kottát miért kell másodpercenként 24-szer kirajzolni?


SAdam hozzászólása 2012-06-09 10:07-kor

Mert ha az egérrel megfogsz egy hangjegyet és elkezded mozgatni, akkor szeretnénk, ha az adott objektum arrébb mozdulna (sőt, ha olyan hangjegyről van szó, amiből mindenféle vonalak és egyéb furcsaságok indulnak el, amik más hangjegyeknél érnek véget, akkor ezeket is szeretnénk, ha egérmozgás közben a rendszer "újratervezné"). Ehhez persze nem kell az egész kottát újrarajzolni, elég csak megmondani, hogy az a Layer, ami az adott hangjegyet reprezentálja, menjen arrébb. Nos, ez valamiért a jelenlegi "beépített" canvasnak annyi számításába kerül, hogy 100%-ra dobja a CPU használatot, ezért döntöttünk úgy, hogy csinálunk sajátot. Azt egyébként sajnos nem tudom szabályozni, hogy milyen sűrűn rajzolja újra a canvast, sőt, még azt sem nagyon tudom szabályozni, hogy melyik részét rajzolja újra, és melyiket cache-elje. Szóval mivel egy vélhetően nem túl optimális környezet rabjai vagyunk, ezért igyekszem mindent optimalizálni, amit csak tudok...


UPi hozzászólása 2012-06-09 10:46-kor

Ha ez az "canvas" olyan, mint amire gondolok, akkor nem tud layer-t újrarajzolni, csak téglalap területet. Szóval innen fúj a szél.


SAdam hozzászólása 2012-06-09 11:26-kor

(Válaszképp erre)

Jó kérdés. Sajnos, mivel zárt forráskódú a környezet, ezért ezt sosem fogom megtudni. Minden esetre a beépített canvast utoljára a 4-es verzióban frissítették (egyébként azért nem tudom, hogy mi lehet a helyzet, mert már abban is volt egy "sprite" opció, ami funkcionalitásában gyakorlatilag ugyanaz, mint a Cairo-alapú API-ban a Layer), miközben a Cairo API használata csak az 5-ös verzió óta érhető el a third-party fejlesztők számára (most egyékbént már a 6-os verziónál tart a cucc, de 5-ről 6-ra nem nyúltak a grafikai dolgokhoz). Summa summarum, egy gány az egész...


SAdam hozzászólása 2012-06-12 17:07-kor

Közben progress van, végül kidobtam az egészet az ablakon, és az UPi-féle transzformációs mátrixos úton indultam el. Már csak azt kell kiagyalnom, hogy egy tetszőlegesnek definiált alakzat átlagos vonalszélességei hogyan transzformálódnak egy tetszőleges affin transzformáció esetén. Matek, matek, matek :-)


UPi hozzászólása 2012-06-12 21:09-kor

Megint elbonyolítja kend :D

Tagek: