Monthly archive for April, 2010

O inicjalizacji zmiennych globalnych

2010-04-28 19:15

Wśród dziwnych błędów wykonania, jakie mogą przytrafić się źle napisanym programom w C++, poczesne miejsce zajmuje przypadek, gdy obiekt zadeklarowany po prostu jako zmienna:

  1. Foo foo;

w rzeczywistości nie istnieje (jeszcze). Dokładniej mówiąc: pamięć na zmienną foo, jest jak najbardziej zaalokowana, ale konstruktor klasy Foo nie został jeszcze wywołany.
Czy taka sytuacja jest w ogóle możliwa? Odpowiedź brzmi: jak najbardziej, jeśli rzeczona zmienna jest globalna lub statyczna w klasie (static) i odwołujemy się do niej podczas konstrukcji innej takowej zmiennej.

Uważa się to za bardzo złą praktykę z prostego powodu: kolejność inicjalizacji zmiennych globalnych/statycznych umieszczonych w różnych jednostkach translacji (czyli plikach .cpp) jest niezdefiniowana. Ci, którzy znają trochę terminologię używaną w standardach C/C++ wiedzą, iż znaczy to tyle, że porządek tej inicjalizacji może się zmieniać w zależności od pory dnia, fazy księżyca i poziomu opadów w dorzeczu Amazonki – czyli w praktyce od platformy, wersji kompilatora czy nawet specyficznych jego ustawień (np. optymalizacji). Nie można więc na niej polegać, bo jeśli nawet “teraz u mnie działa”, to wcale nie oznacza, że za jakiś czas nadal będzie.
Niestety, napisanie kodu zależnego od porządku konstrukcji zmiennych globalnych jest prostsze niż się wydaje. Wystarczy chociażby wyobrazić sobie grę złożoną z kilku podsystemów (dźwięku, grafiki, fizyki, UI, itp.), z których każdy wystawia globalny obiekt będący jego interfejsem. Jeśli rozpoczęcie pracy któregoś z tych podsystemów wymaga innego, już gotowego do pracy (np. UI korzystające z części graficznej), to wówczas pojawią się opisywane wyżej zależności między inicjalizacją obiektów globalnych. A wtedy mamy klops :)

Z problemem, jak to zwykle bywa, możemy poradzić sobie dwojako. Można go obejść, stosując funkcję dostępową do obiektu i zmienną statyczną wewnątrz tej funkcji:

  1. GfxSystem& Gfx() { static Gfx gfx; return gfx; }

Mamy wówczas pewność, że zwróci ona zawsze odwołanie do już skonstruowanego obiektu. Jest to możliwe dzięki własnościom zmiennych statycznych w funkcjach, które sprawiają, że obiekt ten zostanie po prostu utworzony przy pierwszym użyciu (wywołaniu funkcji).
Nazywam ten sposób obejściem, bo w najlepszym razie jest on nieelegancki, a w najgorszym może powodować problemy na drugim końcu życia obiektów, czyli podczas ich destrukcji. Można rzecz rozwiązać lepiej i w sposób bardziej oczywisty, chociaż mniej “efektowny” niż automatyczna konstrukcja obiektów przy pierwszym użyciu.

Mam tu na myśli stare, dobre inicjalizowanie jawne na początku działania programu:

  1. g_Gfx = new GfxSystem();
  2. g_Sfx = new SfxSystem();
  3. g_UI = new UiSystem(g_Gfx); // tu *g_Gfx na pewno istnieje

i równie wyraźne zwalnianie wszystkiego na końcu. Może to wszystko być w samej funkcji main/WinMain, może być w wydzielonym do tego miejscu – te szczegóły nie są aż takie ważne. Grunt, że w ten sposób żadna nietypowa (i niezdefiniowana) sytuacja nie powinna się już nam przytrafić.

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

ma kau se jinvi mi la lojban.

2010-04-26 13:07

…czyli co sądzę o lojbanie.

Powracam do tematu z notki sprzed kilku tygodni, mimo swoich przeczuć, że nie jest to wcale coś, na co wszyscy z napięciem oczekiwali ;-) Spróbuję jednak przekazać porcję swoich nieco głębszych przemyśleń na temat lojbanu po tym, jak poświęciłem trochę więcej czasu na przyjrzenie się temu językowi. Witam więc tych nielicznych, którzy chcą się z nimi zapoznać :]

Dla osoby zupełnie niewtajemniczonej najbardziej rzucającą się w oczy cechą tego języka jest to, że zdecydowana większość słów jest zupełnie niepodobna do swoich odpowiedników w innych językach – mimo że zostały one skonstruowane właśnie z nich. Chociaż na początku może to się wydawać minusem, w ostatecznym rozrachunku ma chyba więcej zalet niż wad. To dlatego, że – jak pisałem już wcześniej – w lojbanie nie ma tradycyjnych części mowy ani konstrukcji gramatycznych. Nie ma więc uzasadnienia dla tego, żeby słownictwo było w nim podobne do innych języków; to mogłoby wręcz być mylące. Zgadza się to też z założeniem kulturowej bezstronności (“znajome” słowa byłyby faworyzowaniem języków europejskich, jak to jest chociażby w esperanto).
Drugim widocznym atrybutem jest wszechobecność krótkich, zwykle dwu- lub trzyliterowych słów, często z apostrofem w środku (czytanym jako ‘h’). To cmavo (wym. szmawo), słowa strukturalne. W nich zawarta jest zdecydowana większość gramatyki lojbanu. Pełnią one mnóstwo różnorodnych funkcji, będąc odpowiednikami zarówno przyimków, spójników czy zaimków, jak i rodzajników, znaków przestankowych, a nawet podkreśleń czy cudzysłowów (w lojbanie wszystko to ma postać słów). Istnieje więc całe multum różnych cmavo, z których wiele jest logicznie zorganizowanych w grupy mające części wspólne, jak np. ze’i/ze’a/ze’u – krótki/średni/długi odcinek czasu. Nie dotyczy to jednak wszystkich, zatem nierzadko pomyłka o jedną literę może skutkować zupełnie innym znaczeniem wypowiedzi lub jej niepoprawnością.

Jednak cmavo są na szczęście w dużej mierze nieobowiązkowe, bo w wielu przypadkach służą uszczegółowieniu tego, co wynika z kontekstu. Jedną z takich kontekstowych informacji jest często czas (teraźniejszy, przeszły, itp.), który w lojbanie – w przeciwieństwie do wielu innych języków – nieczęsto trzeba podawać wprost, bo reguły gramatyki tego nie wymuszają. Jeśli jednak ktoś sobie życzy sobie, aby go doprecyzować, to ma do dyspozycji bardzo ciekawe rozwiązanie, rozdzielające problem na dwie części. Pierwsza odpowiada relacji czasowej między aktualnym punktem a zdarzeniem – czyli temu, czy mówimy o przeszłości (pu), teraźniejszości (ca) czy przyszłości (ba). Druga (zwana niekiedy aspektem) określa, o którym punkcie osi czasu wokół zdarzenia mówimy, np.: przed jego początkiem (pu’o), dokładnie na początku (co’a), w środku jego trwania (ca’o), dokładnie na końcu (mo’u) lub po jego zakończeniu (ba’o). W sumie daje to kilkanaście kombinacji, z których większość da się odnieść na przykład do czasów w języku angielskim (tenses), ale które jednocześnie są o wiele łatwiejsze do zrozumienia.

Trzecia istotną właściwością lojbanu jest fakt, że znacznie częściej niż w innych językach mówimy w nim o “nie-rzeczach”, takich jak zdarzenia, informacje, wypowiedzi, właściwości, ilości, koncepcje, procesy, stany, itp. Nazywa się je tu ogólnie abstrakcjami i regularnie występują one jako argumenty do predykatów (czyli selbri). Istnieją oczywiście ich odpowiedniki w innych językach, lecz zwykle przyjmują one postać zdań podrzędnych (‘to, że…’), jako że rzeczownikami nazywa się w większości rzeczy lub osoby. W lojbanie tak nie jest (no bo przecież rzeczowników nie ma :)) i w sumie wychodzi mu to na dobre. Można rzecz jasna wskazać dziwne “skutki uboczne” – nie można na przykład chcieć (djica) rzeczy jako takich, a jedynie zdarzeń z nimi związanych – ale ogólnie obecność abstrakcji korzystnie wpływa na specyfikowanie tego, o czym tak naprawdę mówimy.

Tags: ,
Author: Xion, posted under Culture, Thoughts » 4 comments

C++ i tablice asocjacyjne

2010-04-23 17:34

Zwykłe tablice (jednowymiarowe) w “normalnych” językach programowania to po prostu ciągłe obszary pamięci, do których pierwszego elementu posiadamy odwołanie. Dostęp do kolejnych polega na korzystaniu z arytmetyki wskaźników, więc niedziwne jest, że takie tablice indeksuje się wyłącznie liczbami – i to z określonego zakresu.
Ci, którzy programowali w na przykład w Pythonie lub PHP znają jednak koncepcję tablic asocjacyjnych, dla których to ograniczenie nie obowiązuje. W C++ do ich symulowania używa się często map z STL-a:

  1. std::map<std::string, Foo*> m;
  2. // ...
  3. m["one"]->DoSomething();

Jest to oczywiście odpowiednie przeciążenie operatora [], a map jest rodzajem pojemnika. Skoro jednak ma to udawać tablicę, to dobrze by było, żeby narzut na obsługę dostępu do elementów nie różnił się zbytnio od tego z prawdziwych tablic. A ten, jak wiemy, jest stały.

Jak to osiągnąć?… Przede wszystkim powinniśmy – jeśli tylko możemy – nie korzystać z kontenera map. Problem z nim polega na tym, że poświęca on dużo uwagi sortowaniu elementów według ich kluczy (“indeksów”). Gdy klucze te są złożone – bo są nimi np. łańcuchy znaków – może to zająć trochę czasu i jednocześnie nie dawać nam żadnej korzyści. Dla tablicy asocjacyjnej najważniejsze są bowiem operacje wyszukiwania wartości (“indeksowania”) i dodawania nowych elementów, względnie ich usuwania. To one muszą działać szybko; wewnętrzny porządek elementów nie jest dla nas istotny.
Dlatego też lepsza jest mapa korzystająca z dobrodziejstw mechanizmu haszowania. W Visual C++ (podobnie zresztą jak w GCC) takie cudo dostępne jest od dawna jako klasa hash_map. Długo było ono niestandardowym rozszerzeniem biblioteki STL, ale wraz z nową wersją standardu staje się ono jego częścią. Poza tym “od zawsze” istnieje też rozwiązanie z Boosta w postaci klasy unordered_map. Przyjemną cechą wszystkich tych implementacji jest niemal jednolity interfejs, tożsamy ze standardową klasą map.

Oprócz używania właściwego pojemnika powinniśmy też wiedzieć, jak go z niego korzystać – a dokładniej mówiąc, czego unikać. Felerną operacją jest próba dodania elementu poprzez zwykłe indeksowanie z przypisaniem:

  1. m["two"] = new Foo();
  2. // klucz "two" nie występuje wcześniej w m

Skutkuje to stworzeniem na chwilę obiektu domyślnego dla danego typu wartości, a potem jego zastąpieniem (przypisaniem) tym właściwym, który ma się w ‘tablicy’ znaleźć. W przypadkach, gdy konstruktor i operator przypisania nie są trywialne, będzie to strata na wydajności. Powinniśmy więc używać raczej metody insert do wstawiania. Może jest to niezupełnie “tablicowe”, ale cóż – albo rybka, albo akwarium ;P

Tags: , , , ,
Author: Xion, posted under Programming » 1 comment

Jak się robi screeny

2010-04-20 16:19

W robieniu zrzutów ekranowych (screenshotów) nie ma, wydawałoby się, nic nadzwyczajnego. Wciskamy po prostu klawisz Print Screen i obraz ekranu ląduje nam w systemowym Schowku, po czym możemy go użyć w dowolny sposób. Przynajmniej tego właśnie się spodziewamy zazwyczaj.
W rzeczywistości robienie screenów to dość śliski temat, jeśli mamy do czynienia z czymś więcej niż zwykłymi aplikacjami okienkowymi. Dotyczy to chociażby pełnoekranowych gier, korzystających z bibliotek graficznych typu DirectX i OpenGL. Wykorzystują one mechanizm znany jako hardware overlay, pozwalający – w skrócie – na ominięcie narzutu systemu operacyjnego związanego z zarządzeniem oknami wielu aplikacji na współdzielonym ekranie monitora. Skutkiem ubocznym jest między innymi to, że wbudowany w system mechanizm tworzenia zrzutów ekranu staje się w tej sytuacji bezużyteczny, jeśli chcemy wykonać screenshota tego rodzaju aplikacji.

Skąd w takim razie biorą się screeny, a nawet całe filmy ze współczesnych gier?… Cóż, istnieją oczywiście metody pozwalające na przechwytywanie obrazów generowanych przez aplikację – można bowiem skłonić ją do wywoływania naszej własnej wersji metody IDirect3DDeviceN::Present zamiast tej wbudowanej :) Brzmi to pewnie tajemniczo i nieco hakersko, ale tak mniej więcej działają programy do przechwytywania wideo typu Fraps.

Do zwykłych screenów zazwyczaj jednak przewiduje się odpowiednie rozwiązania w samych grach. Jednym ze sposobów jest, w przypadku wykrycia wciśnięcia klawisza odpowiadającego za screenshoty (czyli zwykle Print Screen), wyrenderowanie sceny najpierw do tekstury, a ją potem na ekran. Rzeczoną teksturę można później zapisać do pliku.


Aliasing (i nie tylko)
w pełnej krasie

Czy to dobre rozwiązanie? Otóż nie, i to aż z trzech powodów. Stosunkowo najmniej ważnym jest to, iż implementacja może być kłopotliwa, bo wymaga dobrania się do początku i do końca potoku renderingu w aplikacji z kolejnym render targetem. Bardziej istotny jest brak multisamplingu (a więc wygładzania krawędzi wielokątów znanego jako anti-aliasing) w powstałym obrazku. Nie będzie on więc wyglądać zbyt ładnie, już nie wspominając o tym, że nie będzie on odpowiadał temu, co widzimy na ekranie.

Jak więc należy robić screeny? Dokładnie tak, jak nam podpowiada intuicja: należy wziąć to, co widać na ekranie – czyli zawartość bufora przedniego (front buffer) i zapisać to do pliku. W DirectX mamy do tego metodę urządzenia o nazwie GetFrontBufferData, która pobiera nam to jako powierzchnię:

  1. IDirect3DSurface9* screen;
  2. dev->CreateOffscreenPlaneSurface(Width, Height, D3DFMT_A8R8G8B8,
  3.     D3DPOOL_SCRATCH, &screen, NULL);
  4. // screenshot i zapis go do pliku
  5. dev->GetFrontBufferData(0, &screen);
  6. D3DXSaveSurfaceToFile(ScreenFile, D3DXIFF_JPG, screen, NULL, NULL);

Ważne jest, by pamiętać o właściwych rozmiarach powierzchni – takich, jakie ma bufor przedni. W trybie pełnoekranowym odpowiada to buforowi tylnemu, ale dla aplikacji okienkowej wcale nie musi, jeśli rozmiar jej okna możemy zmieniać.

W OpenGL podobną rolę spełnia do GetFrontBufferData spełnia funkcja glReadPixels. Z zapisaniem pobranego obrazu może być tylko “nieco” trudniej ;-)

Cokolwiek, czyli Boost.Any

2010-04-17 11:52

Zasadniczo w C++ zmienne mają jednoznacznie określone typy, znane w czasie kompilacji. Istnieją oczywiście pewne mechanizmy, które zdają się tę zasadę lekko naciągać (dziedziczenie i polimorfizm, szablony), ale bez znaczącej nieścisłości można przyjąć, że jest ona prawdziwa. W języku kompilowanym nie jest to zresztą nic nadzwyczajnego.
Z kolei w językach skryptowych i interpretowanych dość powszechne jest używanie zmiennych bez określonego (tj. jawnie podanego w deklaracji) typu. To, co obecnie przechowuje liczbę, za chwilę może zawierać ciąg znaków czy odwołanie do obiektu i nie ma z tym specjalnego problemu. Typy w takich językach oczywiście istnieją, ale mają je wartości, nie zaś zmienne.

Czasami coś podobnego – czyli zmienna mogąca przechowywać wartości różnego rodzaju – przydaje się i w C++. Wtedy niektórzy przypominają sobie o void*, ale “ogólny wskaźnik na coś” rzadko jest w tym przypadku szczytem marzeń. Jego podstawową wadą jest to, że wymaga on przechowania gdzieś poza nim informacji o docelowym typie wartości, na którą pokazuje – jeśli chcemy ją później wykorzystać, rzecz jasna. Równie poważną jest fakt, że mamy tu do czynienia z wskaźnikiem; pamięć, na którą on pokazuje, musi być kiedyś zwolniona.
Dlatego też lepszym rozwiązaniem jest biblioteka Any z Boosta, umożliwiająca korzystanie ze zmiennych “typu dowolnego”. Reprezentuje go klasa boost::any, mogąca przechowywać wartości prawie dowolnego rodzaju (wymaganiem jest głównie to, by ich typy posiadały zwykły konstruktor kopiujący):

  1. #include <boost/any.hpp>
  2. boost::any any1 = 5, any2 = std::string("Ala ma kota");
  3. boost::any foo = Foo(); // o ile Foo da się kopiować

Ponieważ jednak jest to wciąż C++, a nie język skryptowy, do wartości zapisanych w obiekcie any bezpośrednio dostać się nie da. W szczególności nie można liczyć na niejawne konwersje powyższych zmiennych do typów int, string czy Foo. Zamiast tego korzysta się ze specjalnego operatora rzutowania any_cast, który pozwala na potraktowanie wartości zapisanej w any zgodnie z jej właściwym typem:

  1. int x = 1 + boost::any_cast<int>(any1);

W przeciwieństwie do void*, próba jej reinterpretacji na inny typ danych skończy się wyjątkiem. Nie trzeba jednak polegać na jego łapaniu, jeśli docelowego typu nie jesteśmy pewni: boost::any pozwala też pobrać jego type_info (tj. to, co zwraca operator typeid) bez dokonywania rzutowania.

I to w sumie wszystko jeśli chodzi o tę klasę, a jednocześnie i całą bibliotekę. Mimo swej niewątpliwej prostoty jest ona bardzo przydatna tam, gdzie potrzebujemy zmiennych przechowujących różne rodzaje wartości. Warto więc się z nią zapoznać :)

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

Tower Offence

2010-04-16 19:18

Zamieszczam grę stworzoną przez moją drużynę (Rzeźnicy Inc. :]) w ramach konkursu Compo, który odbył się w zeszłą niedzielę. Jak co roku trwa on około 7 godzin, podczas których należy stworzyć w zasadzie kompletną grę (z dokładnością do ewentualnych, wcześniej przygotowanych frameworków) na zadany temat. Nasza praca zajęła drugie miejsce, przegrywając o włos – w dodatkowym głosowaniu publiczności – ze zwycięską.

[singlepic id=81 w=320 h=240 float=center]

Gra jest wariantem ‘Tower Defence’, czyli obrony przed nadciągającymi hordami wrogów za pomocą strategicznie poustawianych wież. Najważniejszą różnicą jest to, że mamy tutaj gracza, który sam może się po planszy poruszać i strzelać do stworków (i jednocześnie musi ich unikać); stąd zresztą nazwa Tower Offence :) Ponadto budowanie wieżyczek jest możliwe tylko w polach sąsiadujących z aktualną pozycją gracza. Ogólnie chcieliśmy, żeby rozgrywka była bardziej dynamiczna niż w zwykłym TD, a jednocześnie eliminowała jeden bardzo frustrujący element tego rodzaju gier: sytuację, gdy jeden stworek przebija się przez naszą linię obrony, a my nie możemy nic zrobić, jak tylko patrzeć jak powoli idzie w kierunku naszej bazy… Tutaj możemy wtedy wziąć sprawy we własne ręce i po prostu go ustrzelić :)

File: [2010-04-11] Tower Offence  [2010-04-11] Tower Offence (5.7 MiB, 1,789 downloads)

Zapraszam więc do rzucenia okiem na tę produkcję. Niech dodatkową rekomendacją będzie to, że mi osobiście gra się w nią bardzo przyjemnie – a z gatunkiem TD bynajmniej nie przepadam ;P
Do działania gra wymaga aktualnych runtime‘ów DirectX-a.

Tags:
Author: Xion, posted under Events, Games » 5 comments

Na Lodowym Tronie

2010-04-13 22:59

Nieczęsto pozwalam sobie na aż taki off-topic, ale w tym przypadku uważam, że spokojnie mogę być usprawiedliwiony. Jeśli bowiem ktoś grał swego czasu w Warcrafta 3, to pewnie pamięta, że kampania w tej grze (a właściwie dodatku do niej) kończyła się powstaniem jednego z najpotężniejszych antagonistów, jakich świat gier tej doskonałej serii kiedykolwiek widział – w polskiej wersji znanego jako Król Lisz, a w oryginale jako Lich King. Jego długi cień rzucał się potem na pierwszą edycję kolejnej gry z serii, czyli World of Warcraft. Aż w końcu teraz możliwe jest stanięcie mu naprzeciw i rozprawienie się z nim… miejmy nadzieję, że raz na zawsze.

[singlepic id=80 w=320 h=240 float=center]

I to właśnie udało mi się zrobić dzisiaj, co widać na obrazku powyżej. Ci, którzy nie mają specjalnego pojęcia o serii Warcraft mogą zareagować co najwyżej wzruszeniem ramion. Co bardziej hardcore‘owi gracze WoW-a mogą z kolei wskazywać, że to przecież tylko wersja 10-osobowa, na normalnym poziomie trudności i w dodatku o 10% łatwiejsza niż oryginalnie.

Ale ja się tym zupełnie nie przejmuję :) Jak na moje ograniczone możliwości czasowe oraz niewielką zdolność do wciskania klawiszy WSAD oraz przycisków myszki naraz, uważam to za satysfakcjonujące osiągnięcie. A że ma też ono dla mnie pewne znaczenie symboliczne, pozwalam sobie podzielić się z nim ze światem.

Tags:
Author: Xion, posted under Events, Games » 3 comments
 


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