Monthly archive for July, 2007

O dwóch funkcjach do ścieżek

2007-07-31 14:53

Kolejnymi kawałkami kodu z poprzedniej wersji mojej biblioteki uniwersalnej, którym postanowiłem się przyjrzeć, były różnego rodzaju funkcje “systemowe”. Nazwa jest może trochę na wyrost, jako że dotyczyły one głównie pewnych operacji na plikach i ich nazwach. Wśród z nich znalazły więc procedury do pobierania nazw plików, ich rozszerzeń, sprawdzania istnienia plików, tworzenia całych ścieżek katalogów i tym podobne.

Ostały się też dwie najciekawsze funkcje o bardzo podobnych do siebie sygnaturach:

  1. String AbsoluteToRelativePath(const String& strBase, const String& strTarget);
  2. String RelativeToAbsolutePath(const String& strBase, const String& strTarget);

Wykonują one interesującą pracę: konwertują ścieżki względne na bezwzględne i odwrotnie. Jak wiadomo, ścieżka bezwzględna określa położenie w systemie plików w sposób jednoznaczny, np.:

  • C:\Program Files\Taphoo

oznacza podkatalog Taphoo w katalogu Program Files na dysku C – niezależnie od tego, gdzie się teraz znajdujemy. Natomiast ścieżka względna – taka jak ta:

  • ../images/logo.gif

mówi jedynie, że w celu dostania się do pliku logo.gif należy wyjść do nadrzędnego katalogu (..), a stamtąd przejść do katalogu images.

Do czego może przydać się konwersja między oboma typami ścieżek? Dobrym przykładem jest dyrektywa #include w C(++), która akceptuje ścieżki względne:

  1. #include "../Base/StrUtils.hpp"

Preprocesor musi jest rozwinąć, aby móc wstawić zawartość dołączanego pliku. Z użyciem wymienionej wyżej funkcji RelativeToAbsolutePath można łatwo dodać podobną dyrektywę do języka opisu lub skryptowego.

Tags: , ,
Author: Xion, posted under Programming » 4 comments

Błysk Flasha

2007-07-31 9:03

Dawno temu programowanie było jedynym sposobem na sklecenie jakiejkolwiek gry. Przed mniej więcej dziesięciu laty pojawiły się tzw. clicki, czyli programy mające w założeniu umożliwić tworzenie nawet skomplikowanych gier każdemu bez konieczności nauki programowanie. Idea może i szczytna, ale raczej mało realna, więc nic dziwnego że większość programów z tej kategorii ma dość żałosne możliwości.

Jest tylko jeden zadziwiający wyjątek, który jednak pierwotnie był pomyślany do zupełnie innych celów. Mówię o Flashu: początkowo przecież technologia ta była przeznaczona do tworzenia wektorowych animacji, np. bannerów reklamowych. Teraz wbudowany tam język ActionScript jest tak rozwinięty, że gry flashowe na stronach WWW już dawno przestały dziwić.

Screen z gry 3d Logic Screen z gry Tangerine Panic Screen z gry Flight of the Hamsters Screen z gry N

Ostatnio zobaczyłem jednak coś zupełnie niespotykanego. Mam tu na myśli grę Dofus, napisaną we Flashu i należącą do gatunku… MMORPG. Działa znakomicie, oferując wszystko co wymagamy od gier tego gatunku: obszerny świat do zwiedzania, zróżnicowanie klas, sporo potworków i questów, sprawna komunikacja między graczami, granie w drużynach, przynależność do gildii, itd. Niesamowite, że wszystko to działa na warstwach graficznych i jest kontrolowane ActionScriptem!

Screen z gry Dofus

Takie produkcje sprawiają, że czasem można lekko zwątpić, czy programowanie jest oby na pewno słuszną drogą… Na szczęście wystarczy spojrzeć na kilka dobrych, “normalnych” gier żeby wiara powróciła ze zdwojoną mocą :)

(Cztery gry ze screenów można zobaczyć tutaj: 3D Logic, Tangerine Panic, Flight of the Hamsters, N.)

Tags:
Author: Xion, posted under Games, Internet » 1 comment

Czcionki i tekst(ury)

2007-07-30 9:08

Żadna porządna biblioteka do grafiki 2D nie może obyć się bez narzędzi służących do wypisywania tekstu. Kiedy jednak mamy na uwadze głównie programowanie gier (lub pokrewnych aplikacji), sprawa wygląda nieco inaczej niż w bardziej “ogólnych” zastosowaniach. Nie trzeba na przykład rozkładać tekstu na czynniki pierwsze:

Linie pisma

Pozwalałoby to oczywiście w razie potrzeby dodać kursywę, pod-, nad- i przekreślenie. Zazwyczaj jednak nie jest to potrzebne.

Tak więc chociaż wygląda to na krok wstecz, stosuje się najczęściej czcionki bitmapowe. Pomysł polega na tym, że cały używany zestaw znaków danego kroju i danej wielkości jest rysowany na jednej teksturze w taki sposób, by łatwo można było obliczyć pozycję każdego znaku:

Tekstura czcionki Arial stworzona programem Bitmap Font Builder Tekstura czcionki Verdana stworzona programem Bitmap Font Generator

Wyświetlanie napisu polega wtedy na renderowaniu oteksturowanych prostokątów – po jednym dla każdego znaku. Jest to bardzo wydajne, bo chociaż trójkątów może być bardzo dużo, to ich tekstura jest zawsze taka sama. Można zatem wyrzucić całe połacie tekstu na ekran tylko jednym wywołaniem Draw[Indexed]Primitive.

Tylko skąd wziąć taką sprytną teksturę? Można rzecz jasna generować ją samemu przy pomocy funkcji GDI; choć jest to wolne, byłoby wykonywane tylko raz, więc nie stanowiłoby problemu. Lepszym rozwiązaniem jest użycie odpowiednich programów, z których najlepszym jest chyba Bitmap Font Generator. Potrafi on w sprytny sposób upakować w jednym obrazku sporą ilość znaków, zaś ich parametry opisuje w łatwym do odczytania formacie tekstowym przypominającym szczątkowy INI.

Obecnie używam więc właśnie jego i dzięki temu mogłem w końcu dodać do swojego silnika podstawowy i zupełnie niezbędny element: licznik FPSów :)

Pieszy, kryj się!

2007-07-28 8:56

Zielony listekJedyne co chciałbym zrobić w niniejszej notce, to bezwstydnie się pochwalić :) Sądzę bowiem, że okazja jest najzupełniej wystarczająca. Wprawdzie mam już za sobą maturę i cztery sesje egzaminacyjne na studiach, jednak muszę przyznać, że były one z spacerkiem w porównaniu z egzaminem na prawo jazdy :]

Na szczęście wczoraj udało mi się przejść i przez to – za drugim razem! Skutek jest oczywiście taki, że po tym wydarzeniu niezbyt bezpieczne polskie drogi staną się pewnie jeszcze gorsze ;D


Author: Xion, posted under Life » 6 comments

Traktat o wyższości klawiatury

2007-07-26 19:11

Używanie edytora vim zawsze uważałem za rodzaj białej magii. Sam program to przykład bodaj najbardziej oszczędnego interfejsu użytkownika, jaki można sobie wyobrazić – zaledwie jedna linijka tekstu na polecenia. I mimo tego, że prawie nic tam nie widać, można w edytorze zrobić prawie wszystko.
W nieco bardziej współrzędnych aplikacjach mamy na szczęście nieco bardziej rozwinięte sposoby sterowania. Paski menu, paski narzędzi (a ostatnio wzorowane na Office 2007 jakieś dziwne hybrydy tychże), dokowalne okienka opcji, gesty myszy, i tak dalej. Wszystkie opcje są więc widoczne i “doskonale dostępne”, jak zwykle twierdzą twórcy.

Mimo tych wszystkich wynalazków cały czas każdy dobrze skonstruowany program można obsługiwać najszybciej przy pomocy klawiatury. Wprawdzie polecenia tekstowe wyszły już z mody (i zbytnio mi to nie przeszkadza), ale inne narzędzia ciągle istnieją i mają się świetnie. Mówię tu o często niedocenianych przez nowicjuszy skrótach klawiaturowych.
Przyznam szczerze, że dopiero niedawno zbliżyłem się do punktu, który można nazwać w miarę sensownym wykorzystaniem potencjału tych udogodnień. Nie mówię tu rzecz jasna o takich oczywistościach jak Alt+F4 :) Chodzi mi raczej o bardziej przydatne skróty czy nawet same klawisze – jak choćby:

  • Win+D, Win+R – w samym systemie operacyjnym
  • Home, End, Page Up, Page Down – do poruszania się w tekście
  • Shift + ew. Ctrl + strzałki – do zaznaczania
  • Ctrl+(+), Ctrl +(-) – do przeskakiwania między miejscami w kodzie w Visual Studio
  • F7, Ctrl+F7, F5, Ctrl+F5 – do kompilacji i uruchamiania programów w VS
  • F9, F10, F11 – do debugowania w VS

Korzystanie z nich – a także z wielu innych użytecznych kombinacji – jest naprawdę bardzo wygodne. Kto wie, może jeśli sterowanie głosem stanie się doskonalsze, to przy jego wykorzystaniu w połączeniu ze skrótami (szybkość!) będziemy mogli ostatecznie pozbyć się gryzoni :) No, pomijając oczywiście gry!

Tags: ,
Author: Xion, posted under Computer Science & IT » 1 comment

Trzy rozwiązania dla relacji zawierania

2007-07-25 13:47

Chcę dzisiaj zająć się pewną sprawą natury projektowej. Mam tu mianowicie na myśli sytuację, gdy mamy do czynienia ze zbiorem różnych elementów, wywodzących się z tej samej klasy bazowej. Najlepiej widać to będzie na odpowiednim diagramie:

Relacja zawierania

 

Jest tu klasa kontenera (Container), która posiada m.in. zestaw różnych Elementów. Ich dokładne typy nie są i nie muszą być znane – ważne, że wywodzą się od klasy bazowej Element. W szczególności klasy Container i Element mogą być jednym i tym samym – wtedy będziemy mieli do czynienia ze strukturą hierarchiczną, reprezentującą np. drzewo węzłów dokumentu XML czy kontrolki systemu GUI. Zauważmy jeszcze, że każdy element wie o swoim właścicielu (pole Owner), czyli o pojemniku, który go zawiera (albo o elemencie nadrzędnym w przypadku drzewa).
Z obsługą już dodanych elementów nie ma zbytniego problemu, jako że wyjątkowo naturalne jest tu wykorzystanie metod wirtualnych i polimorfizmu. Kłopot polega właśnie na tworzeniu i dodawaniu nowych elementów – a ściślej na interfejsie (funkcjach i parametrach), które mają do tego służyć. I tutaj właśnie pojawiają się te trzy rozwiązania.

Rozwiązanie pierwsze polega na wyposażeniu klasy Container w garnitur metod zajmujących się tworzeniem każdego typu elementów i dodawaniem ich do zbioru. W naszym przypadku byłyby to więc funkcje AddElementA, AddElementB i AddElementC:

  1. void Container::AddElementA(parametry)
  2. {
  3. Elements.insert (new ElementA(this /*właściciel nowego elementu */, parametry);
  4. }

Jak widać w tym przypadku dobrze jest wyposażyć konstruktory klasy Element i pochodnych w parametr, przez który będziemy podawali wskaźnik do nadrzędnego kontenera lub elementu.

Drugie rozwiązanie zakłada, że pojemnik dysponuje tylko jedną ogólną metodą AddElement. Przekazujemy jej już utworzone obiekty, a jej zadaniem jest tylko dodać je do już posiadanych:

  1. Container->AddElement (new ElementA(parametry));

Zauważmy, że tutaj konstruktory elementów już nie przyjmują wskaźników na swoich właścicieli. Dzięki temu możemy uchronić się przed omyłkowym dodaniem elementu do innego pojemnika niż ten, który podaliśmy podczas jego tworzenia.
Skąd jednak element wie, do kogo należy?… Za to odpowiada już metoda dodająca, która modyfikuje pole określające właściciela:

  1. void Container::AddElement (Element* pElem)
  2. {
  3. Elements.insert (pElem);
  4. pElem->m_pOwner = this;
  5. }

Oczywiście w tym celu klasa Container musi być zaprzyjaźniona z klasą bazową Element, co na szczęście w C++ nie stanowi problemu :) (W innych językach, jak np. C# czy Java, można osiągnąć bardzo podobny efekt przy pomocy pakietów.)

W końcu trzecie wyjście przenosi cały ciężar pracy na same elementy. W tej koncepcji to elementy same się dodają do podanego pojemnika:

  1. // konstruktor
  2. Element::Element(Container* pCont, parametry) : m_pCont(pCont)
  3. {
  4. if (pCont) pCont->AddElement (this);
  5. }

Żeby miało to sens, muszą być one aczkolwiek tworzone dynamicznie poprzez operator new.

Czas na nieodparcie nasuwające się pytanie, czyli… które rozwiązanie jest najlepsze? No cóż, sprawa nie jest taka prosta (inaczej bym o niej nie pisał ;D), więc może przyjrzyjmy się kilku argumentom:

  • Rozwiązanie nr 1 jest dość powszechnie stosowane zwłaszcza tam, gdy elementy mają ustalone i raczej niezmienne typy. Przykładem jest wspomniane drzewko węzłów XML: tam wachlarz typów ogranicza się do tekstu, elementów XML, atrybutów, sekcji CDATA, instrukcji przetwarzania i jeszcze paru innych. Napisanie dla każdego z nich odpowiedniej metody Add* nie jest specjalnie czasochłonne.
  • Trzecie rozwiązanie jest najwygodniejsze i daje najmniejsze pole manewru, jeżeli chodzi o popełnienie błędów – np. stworzenie obiektu i niedodanie go do pojemnika. Z drugiej strony może ono produkować nieintuicyjny kod. Jeżeli bowiem chcemy tylko dodać jakiś element i nie jest on nam za chwilę potrzebny, to użyjemy poniższego kodu:
    1. new ElementA(pContainer, parametry);

    Jest więc new, którego rezultat ignorujemy i tylko po dłuższym przyjrzeniu się można zauważyć, że to jednak nie jest wyciek pamięci.

  • Zarówno trzecie, jak i (zwłaszcza) drugie rozwiązanie dopuszcza tworzenie elementów “wolnych”, nieprzypisanych do żadnego pojemnika. Wbrew temu co napisałem przed chwilą, taka czynność nie musi być wcale błędem. W takim przypadku odwołanie do właściciela w elemencie ustawiamy po prostu na NULL.

Cóż więc wybrać? Ostatnio skłaniam się ku drugiemu rozwiązaniu. Według mnie jest ono z tych trzech najbardziej przejrzyste – tworzenie obiektu to jedno (operator new), a jego dodawanie drugie (metoda Add). Ponadto dzieli ono pracę w miarę równo między element, jak i pojemnik – w przeciwieństwie do pozostałych modeli. I w końcu, nic w tym rozwiązaniu nie jest robione za plecami programisty, co oczywiście może być zaletą, ale i wadą oznaczającą, że przecież wszystko musimy zrobić samemu.

A zatem… kwadratura koła? W takich sytuacjach warto chyba iść na kompromis, czyli poprzeć prostokąt z zaokrąglonymi rogami ;)

Tags: , ,
Author: Xion, posted under Programming » 2 comments

Obiekty są pamiętliwe

2007-07-24 14:47

Czasami mylne wyobrażenie o pewnych elementach języka programowania potrafi ściągnąć na nas nie lada kłopoty. Właśnie ostatnio mogłem o się o tym przekonać, a sprawa dotyczy niezbyt często wykorzystywanego elementu C++, a mianowicie placement new.

Cóż to takiego? Jest to sposób na podanie operatorowi new adresu miejsca w pamięci, który ma wykorzystać. Może to brzmieć niedorzecznie, bo przecież new ma za zadanie właśnie alokację pamięci. To jednak tylko część prawdy, bowiem ten operator wywołuje też konstruktor tworzonego obiektu. W przypadku wersji placement będzie to więc jedyna czynność, jaką wykona.
Składnia placement new wygląda mniej więcej tak:

  1. CFoo* = new (pvAddress) CFoo(...);

Na czym jednak polega problem? Otóż tego specjalnego wariantu operatora new nie można wywoływać bezkarnie. W szczególności nie jest to sposób na “przezroczyste” wywołanie konstruktora dla jakiegoś obiektu.

Pytanie oczywiście brzmi: do czego było mi potrzebne użycie tego rzadkiego mechanizmu języka? No cóż, teraz już wiem, że do niczego, jednak wcześniej myślałem, że będzie to niezły sposób na wczytywanie zasobów.
W skrócie: w mojej bibliotece zasoby takie jak np. tekstury czy bufory tworzą dość prostą hierarchię dziedziczenia. Parametry niektórych z nich – jak np. tekstur – można wczytywać z pliku tekstowego – w tym przypadku chodzi chociażby o nazwę pliku graficznego z obrazkiem tekstury.
Sęk w tym, że zasób tekstury (reprezentowany przez klasę CTexture) jest też zasobem DirectX (dziedziczy po IDxResource). A z tym związane są kolejne parametry, jak np. pula pamięci czy sposób użycia zasobu (to akurat nie jest mój wymysł, tylko DirectXa ;P). One również mogą być zapisane w pliku i trzeba je uwzględnić przy wczytywaniu tekstury.

W moim “genialnym” rozwiązaniu wymyśliłem więc, że podczas tworzenia obiektu CTexture zostanie najpierw stworzony obiekt IDxResource (dzięki czemu zostanie załatwiona kwestia “DX-owych” parametrów). Następnie w tym samym miejscu pamięci – placement new! -skonstruujemy obiekt CTexture, który zajmie się załadowaniem pozostałych danych.
I to rzeczywiście działało, dopóki się nie zorientowałem, że zapomniałem dopisać do menedżera zasobów kodu, który zwalniałby wszystko przy kończeniu programu. Wtedy to w ruch poszły destruktory, a wtedy pojawiły się… przerwania systemowe informujące o uszkodzeniu sterty.

Powodem jest fakt, że obiekty (a ściślej mechanizm ich alokacji) pamiętają sposób, w jaki zostały stworzone. Jeżeli było to zwykłe new, to zniszczenie obiektu pociąga za sobą zwolnienie pamięci. Lecz jeżeli było to placement new, to operator delete zakłada, że nic nie wie o obszarze pamięci, w którym obiekt rezyduje – więc go nie zwalnia. W istocie FAQ C++ wyraźnie pisze, by przy używaniu placement new samemu wywoływać destruktor, a potem samodzielnie zwalniać pamięć.
placement new nie jest po prostu sposobem na wywołanie konstruktora – z jego użyciem wiążą się określone konsekwencje. Jak widać czasami można przekonać się o nich w mało przyjemny sposób :)

Tags: , ,
Author: Xion, posted under Programming » 2 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.