Czasami czytając gazety i serwisy internetowe (ewentualnie śledząc to, co podaje telewizja), wydaje się, że jakiś temat przewija się nadspodziewanie często. Jak to ładnie ktoś powiedział, w końcu można zacząć bać się otwierać konserwę, aby na niego nie natrafić :) Z początku jednak można nie zdawać sobie z tego sprawy i dopiero któryś z rzędu artykuł uświadamia nam: “Ej, przecież już czytałem o czymś podobnym!”.
W tym przypadku moją uwagę zwrócił ten tekst publikowany na Onecie. Opowiada on o grach sieciowych, a konkretniej o MMORPGach, a jeszcze konkretniej o grze World of Warcraft i grającej w nią pewnej dziewczynie. Muszę przy tym zaznaczyć, że nie jest to kolejny tekst z tezą streszczającą się w słowach: “jakie te gry są brutalne, bezwartościowe i w ogóle złe”, tylko całkiem porządne przedstawienie tematu. A tematem jest oczywiście uzależnienie od wspomnianych gier.
Przypomniało mi się wówczas, że w zeszłotygodniowym Newsweeku (37/2007) widziałem reportaż opisujący nałóg… tworzenia i utrzymywania kolejnych stron w Wikipedii. Okazuje się, że mechanizm funkcjonowania tej encyklopedii, w której każdy może edytować treść, ma swoją drugą stronę. Ktoś może bowiem tę treść po prostu zepsuć i aby temu przeciwdziałać, autor danego hasła z konieczności staje się jego strażnikiem. A jeżeli doda kolejne i kolejne strony, nagle może się okazać, że ich pilnowanie zajmuje mu większość wolnego (i nie tylko wolnego) czasu.
Na deser mogę jeszcze przytoczyć dzisiejszy nius z cyklu ‘Ktoś z Dalekiego Wschodu umarł po iluśtam godzinach grania’. Tym razem był to Chińczyk i jego “wynik” to trzy dni, a wszystko jak zwykle rozegrało się w anonimowej kafejce internetowej, gdzie nikt na nikogo nie zwraca uwagi.
Słowo-klucz łączące te wszystkie treści to oczywiście nałóg lub ewentualnie uzależnienie. Ich rodzajów przybywa ostatnio w niewiarygodnym tempie. Można powiedzieć, że “zwykłe” papierosy, alkohol czy narkotyki to już oklepane banały, a teraz “w modzie” jest co najmniej uzależnienie od wspomnianych gier, komputera czy Internetu albo pracy, zakupów, clubbingu, szybkiej jazdy samochodem, plotkowania czy jedzenia.
Dziwna to lista, prawda? Aż chciałoby się do niej dodać – powiedzmy – telefony, komunikację miejską, gazety… A zresztą, nie powinniśmy się ograniczać: należy jak najszybciej rozpoznać kliniczne objawy uzależnienia od elektryczności, wodociągów i centralnego ogrzewania :P Jak widać w miarę rozwoju medycyny faktycznie zaczyna się okazywać, że nikt tak naprawdę nie jest już zdrowy :)
Żeby więc nie pozostać w tyle, pragnę niniejszy oświadczyć, że sam jestem uzależniony od:
Lista ta dotyczy aczkolwiek tylko jednej dziedziny życia i z pewnością nie jest kompletna. Już po niej widać jednak, że mojemu zdrowiu zagraża całe mnóstwo paskudnych nałogów, z którymi powinienem czym prędzej zacząć walczyć.
Ale jak?… Chyba dla odmiany trzeba by zająć się z powrotem graniem w WoW ;D
Wśród standardowych kontrolek spotykanych w każdym systemie graficznego interfejsu jest jedna, która zdecydowanie wyróżnia się stopniem skomplikowania. Z wierzchu to tylko mały, podłużny prostokąt z wpisanym ciągiem znaków, który można modyfikować. Jednak mechanizmy sprawiające, że jest to możliwe, nie są wcale takie proste.
Mówię tu oczywiście polu tekstowym, znanym też jako textbox, editbox lub po prostu edit.
Powodów, dla których właśnie ta kontrolka wyróżnia się złożonością, jest przynajmniej kilka. Są to chociażby:
Tak naprawdę choćby w systemie Windows pola tekstowe umożliwiają nieco więcej, jak na przykład możliwość cofnięcia ostatniej operacji, obsługa Schowka czy nawet menu kontekstowe. Takie cuda nie są chyba jednak potrzebne do szczęścia :)
Zauważmy też, że kontrolka umożliwiająca edycję dłuższego tekstu dzielonego wiersza to coś zupełnie innego niż zwykły textbox i jej możliwości są nieporównywalnie większe. Dochodzą tam bowiem paski przewijania, zaznaczanie rozciągnięte na kilka wierszy, i tak dalej.
Implementując proste pole tekstowe zauważyłem, że bardzo pomaga przy tym odpowiednia konstrukcja pewnych podstawowych mechanizmów GUI – jak mouse capture czy fokus klawiatury, modelu propagacji zdarzeń oraz modułu odpowiedzialnego za wypisywanie tekstu. Łącznie jednak wychodzi z tego i tak nadspodziewanie duża ilość kodu ze sporą liczbą zagmatwanych ifów :]
Trochę poprzednio ponarzekałem na technikę RAII stosowaną w C++, a raczej na towarzyszący jej brak wygodnej instrukcji finally
. Było to być może odrobinę niesprawiedliwe, gdyż mechanizm ma dość duże możliwości – przynajmniej potencjalnie :)
Natrafiłem jakiś czas temu na Usenecie na ciekawy pomysł związany z tą właśnie techniką. Chodzi tu o wykrywanie, z jakiego powodu obiekt lokalny ma zostać zniszczony. Może się to bowiem odbyć w normalny sposób (gdy kończy się wykonanie odpowiedniego bloku kodu i program przechodzi dalej) lub w wyniku odwijania stosu podczas obsługi wyjątku. W obu przypadkach w C++ jest jednak wywoływany jeden i ten sam destruktor.
Jest to w porządku, jeżeli jego zadaniem jest tylko zwolnienie zasobu (czyli np. zamknięcie otwartego deskryptora pliku). Możemy sobie aczkolwiek wyobrazić zastosowanie RAII do tzw. transakcji:
Transakcja to termin znany głównie programistom zajmującym się bazami danych lub innymi pokrewnymi dziedzinami “sortowania ogórków” ;) W skrócie, jest to taki ciąg operacji, który musi być zaaplikowany albo w całości, albo wcale. Jeżeli po drodze zdarzy się coś nieoczekiwanego i transakcję trzeba przerwać, wszystkie wykonane do tej chwili operacji powinny zostać odwrócone (rollback).
Można by to zrobić automatycznie w reakcji na rzucenie wyjątku, gdyby C++ pozwalał na wykrycie wspomnianych dwóch sposobów niszczenia obiektu. Ciekawym pomysłem na to jest dopuszczenie więcej niż jednego destruktora:
Zwykły, bezparametrowy, byłby wywoływany w przypadku zwyczajnego opuszczenia bloku kodu. Natomiast destruktor przyjmujący parametr włączałby się wówczas, gdy niszczenie obiektu zdarzy się z powodu wyjątku. Taki destruktor “łapałby” więc na chwilę taki wyjątek – lecz nie po to, by go obsłużyć, ale wyłącznie w celu odpowiedniego zakończenia życia obiektu w sytuacji kryzysowej. Parametr takiego destruktora odpowiadałby typowi wyjątku, który ten destruktor miałby “łapać”.
Obecnie nie jest naturalnie możliwe stosowanie w C++ takiej konstrukcji. Istnieje jednak sposób na sprawdzenie, czy jesteśmy właśnie w trakcie obsługi jakiegoś wyjątku. Służy do tego mało znana funkcja uncaught_exception
z przestrzeni std
(nagłówek exception):
Wprawdzie nie zapewnia ona dostępu do samego obiektu wyjątku (ani poznania jego typu), ale pozwala na zorientowanie się, czy taki wyjątek w ogóle wystąpił. A to, jak widać, najczęściej wystarczy. Tak więc chociaż przeciążanie destruktorów na pierwszy rzut oka brzmi interesująco (i intrygująco), nie jest, jak sądzę, zbytnio potrzebne.
Właśnie się dowiedziałem, że dzisiaj właśnie przypada Dzień Programisty. A to dlatego, że dziś jest dokładnie 256-ty dzień roku, a znaczenia tej liczby chyba nie trzeba wyjaśniać. Wprawdzie taka okazja mogłaby pasować chyba do każdej informatycznej profesji, ale i tak uważam ją za o niebo lepszą chociażby od rocznicy urodzin naszego tzw. patrona św. Izydora czy innych tego typu pomysłów.
Mamy zatem święto koderów i należałoby je uczcić w jakiś nietypowy sposób. W pierwszej chwili pomyślałem, że tego odmiany mógłbym dla odmiany… nie napisać ani jednej linijki kodu. Niestety, ta sposobność przepadła kilka godzin wcześniej :) A może wobec tego wcielić w życie sentencję głoszącą, iż święto trzeba uczcić pracą? :)
Niezależnie od ich własnych pomysłów na obchodzenie tego dnia, życzę wszystkim programistom oraz tym, którzy dopiero do tego miana aspirują, przede wszystkim powodzenia w realizacji wszelkich koderskich zamierzeń. Wiadomo, że do wszystko dochodzi się głównie ciężką pracą i odpowiednią motywacją, ale czasami przydaje się ta odrobina szczęścia, na którą nie mamy wpływu. Niech dzisiaj (i nie tylko dzisiaj) to szczęście sprzyja wszystkim programistom, aby ta ilustracja po prawej jak najmniej do nas pasowała ;]
Wyjątki są sposobem na zasygnalizowanie nietypowych i niespodziewanych błędów, które poważnie zaburzają działanie programu. I właśnie to, że potencjalnie wyjątek może wystąpić w bardzo wielu miejscach w kodzie, rodzi pewne kłopoty. Problemami są chociażby zasoby: coś, co się pozyskuje, wykorzystuje, a następnie zwalnia, gdyż w przeciwnym razie doszłoby do wycieku. Typowym zasobem jest chociażby dynamicznie alokowana pamięć – jeżeli jej nie zwolnimy, nastąpi klasyczny wyciek, jako że C++ nie posiada garbage collectora, który mógłby się tym zająć za nas.
W C++ zalecanym rozwiązaniem tego problemu jest technika znana jako RAII (Resource Acquision Is Initialization – pozyskanie zasobu jest inicjalizacją). Korzysta ona z faktu, że w naszym ulubionym języku programowania możemy tworzyć obiekty lokalne z konstruktorami i destruktorami. Te drugie wywołają się zawsze przy opuszczaniu danego bloku kodu – niezależnie od tego, czy stało się z powodu wyjątku czy tez normalnego przebiegu programu. Pomysł polega więc na tym, by tworzyć obiekt specjalnie przygotowanej klasy w momencie pozyskania zasobu, zaś destruktor tego obiektu zajmie się już jego zwolnieniem, niezależnie od powodu.
Dopóki korzystamy z pamięci albo z plików, wszystko jest w porządku; odpowiednie klasy (jak auto_ptr
) posiada bowiem Biblioteka Standardowa. Gorzej jeśli chcemy skorzystać z innego rodzaju zasobów. Jeśli odpowiednia klasa realizująca technikę RAII nie istnieje, nie pozostaje nam nic innego, jak samemu ją sobie zapewnić (czytaj: napisać). I tak dla każdego rodzaju niestandardowych zasobów, które używamy. Po niedługim czasie można by z tych klas ułożyć własną “bibliotekę standardową” ;)
Alternatywą dla RAII jest dodanie trzeciego bloku (po try
i catch
) do konstrukcji łapiącej wyjątki. Jest on zwykle nazywany finally
. Instrukcje zawarte w tym bloku są wykonywane zawsze po tych z bloku try
– niezależnie od tego czy wyjątek wystąpił czy nie. Jest to więc bardzo dobre miejsce na wszelki kod zwalniający pozyskane wcześniej zasoby, np.:
Co ciekawe, posiadają go języki, które jeden z najważniejszych zasobów – pamięć – mają zarządzaną przez odśmiecacz, który praktycznie wyklucza możliwość powstania wycieków. Rzecz jednak w tym, że niektóre zasoby, jak chociażby otwarte pliku, nie mogą sobie czekać na to, aż odśmiecacz przypomni sobie o nich, gdyż wtedy byłyby blokowane stanowczo zbyt długo.
Czy C++ też potrzebuje instrukcji finally
? Na pewno nie jest to bardzo paląca potrzeba, jako że technika RAII zapewnia komplet potrzebnej tutaj funkcjonalności. To drugie, alternatywne rozwiązanie ma jednak szereg zalet:
finally
można też pożytecznie wykorzystać nawet wówczas, gdy w grę nie wchodzi możliwość pojawienia się wyjątku. Jeżeli na przykład w jakiejś skomplikowanej funkcji mamy wiele miejsc, w których może nastąpić jej zakończenie, a przy każdej okazji może być potrzeba wykonania jeszcze jakichś czynności końcowych. Wtedy moglibyśmy zamknąć całą treść funkcji w try
, a owe czynności umieścić w sekcji finally
. Trzeba by się było jednak liczyć z tym, że blok try
nie jest darmowy i jego użyciu nakłada pewien narzut.I przede wszystkim: RAII i finally
nie wykluczają się nawzajem. Dlatego obecność tego drugiego mechanizmu w C++ na pewno by nam nie zaszkodziła :)
Kończąc powoli prace nad systemem GUI (a przynajmniej jakimiś sensownymi podstawami tego systemu), zakańczam jednocześnie prace nad “płaską” częścią silnika. Innymi słowy, już wkrótce nie będzie żadnej wymówki i trzeba będzie zabrać się za dodanie upragnionego, a zarazem niezwykle komplikującego życie trzeciego wymiaru :)
Pomyślałem jednak, że najpierw dobrze byłoby poskładać napisane już w cegiełki w sensowną całość i stworzyć coś w rodzaju frameworka. Chodzi tutaj o tę warstwę pośrednią między kodem silnika a użytkownikiem i systemem, czyli szkielet umożliwiający wygodne tworzenie rzeczywistych aplikacji.
W wielu bibliotekach różnie to rozwiązano. Z jednego strony DirectX czy OpenGL zostawiają to całkowicie w gestii programisty. Musi on samodzielnie przygotować chociażby to okienko, w którym będzie się odbywało rysowanie. Z kolei np. SDL bardzo głęboko ingeruje w kod programu, narzucając nawet określoną formę funkcji main
.
Najlepsze jest oczywiście takie rozwiązanie, które zapewnia zarówno dużą elastyczność, jak i nie zmusza do napisania kilkuset linijek w celu zobaczenia czegokolwiek. Wydaje mi się, że bliski ideałowi jest pomysł zamknięcia funkcjonalności frameworka w klasę w rodzaju Application
, zawierająca metody pozwalające na inicjalizację programu i późniejsze sterowanie nim. Metody tej klasy byłyby wywoływane w funkcji startowej programu, czyli main
lub WinMain
. Tak to wygląda na przykład w Windows Forms czy w VCL (Delphi):
[delphi]program SomeProject;
uses
Forms,
MainFrm in ‘MainFrm.pas’ {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.[/delphi]
Według mnie najlepiej jest, gdy obiekt głównej klasy programu jest albo statycznie tworzonym singletonem, albo zostaje wykreowany przez programistę przy rozruchu aplikacji. Najważniejsze, aby nie zmuszać do dziedziczenia po klasie Application
– po to na przykład, by nadpisując metody wirtualne zapewnić możliwość reakcji na zdarzenia (jak wciśnięcia klawiszy czy ruch myszy). Dzięki delegatom, choćby tym z biblioteki FastDelegate, można to zrobić dokładnie tak, jak w “bardziej cywilizowanych” językach programowania.
Rysując grafikę dwuwymiarową dość często chcemy, aby była ona wpasowana tylko w ograniczony obszar ekranu. Jeśli większość obrazu ma być wyrenderowaną trójwymiarowa scena, grafika HUDa nie może zajmować zbyt dużo miejsca. Wielkość rysowanych sprite’ów można kontrolować na wiele sposobów, począwszy od zabawy z macierzami (projekcji i/lub świata), a skończywszy na programowym skalowaniu wielokątów.
Podobnie jest z innym aspektem tego ‘ograniczania’ wyświetlanej grafiki, czyli przycinaniem. Tutaj jednak dużo zależy od tego, jakie kształty chcemy przycinać oraz czym jest region przycinania.
Najprostszy przypadek dotyczy ograniczenia prostokąta przez inny prostokąt. Przycinanie, czyli wyznaczenie ich części wspólnej, to w tym przypadku tylko kilka porównań współrzędnych tych prostokątów:
Ponadto trzeba jeszcze tylko sprawdzić, czy powstały prostokąt jest poprawny – czyli czy spełnione są warunki: left
mniejsze od right
i top
mniejsze niż bottom
. W przeciwnym wypadku wyjściowe prostokąty nie mają części wspólnej.
Kiedy prostokątem chcemy uciąć coś o innym kształcie, wówczas sprawa znacznie się komplikuje z matematycznego punktu widzenia. Można jednak skorzystać z przydatnego narzędzia wbudowanego w Direct3D i znanego jako scissor test. Jest to specjalna, późna faza potoku renderowania, która odrzuca wszystkie piksele poza tymi mieszczącymi w podanym regionie (w przypadku DirectX 9 chodzi o prostokąt). Skorzystanie z tego mechanizmu jest bardzo proste i ogranicza się do:
D3DRS_SCISSORTESTENABLE
na TRUE
SetScissorRect
urządzenia (współrzędne prostokąta liczone są w pikselach według viewportu)A co zrobić w najbardziej skomplikowanym przypadku: gdy zarówno przycinane figury, jak i region przycinania są niestandardowymi kształtami? Cóż, DirectX to nie GDI i nie można oczekiwać, że będzie miał wszystko :) Nie jest to aczkolwiek sytuacja bez wyjścia, gdyż można ją rozwiązać przy pomocy stencil buffera (‘bufora szablonu’ lub ‘bufora matrycy’, że wymienię co ciekawsze tłumaczenia, jakie dotąd spotkałem). Wymaga to jednak nieco zabawy ze stanami urządzenia, a przede wszystkim renderowania regionu przycinania w postaci prymitywów od nowa w każdej klatce.