Posts from 2007

Nałóg wynajdywania nałogów

2007-09-17 23:03

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!”.

Screen z World of WarcraftW 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:

  • regularnego publikowania nowych notek na blogu (od tego przede wszystkim!)
  • codziennego czytania nowych cytatów z IRCa na bash.org.pl
  • przeglądania nowych wątków na forum Warsztatu i odpisywania na nie
  • uporczywego i niekończącego się dodawania nowych możliwości do swojej silniko-biblioteki C++
  • wymyślania i spisywania pomysłów na nowe programy, które potencjalnie mógłbym kiedyś zrealizować
  • realizowania mniej więcej 1% tych pomysłów :)
  • bezproduktywnego marnowania czasu na warsztatowym kanale IRC w tak zwanym międzyczasie

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


Author: Xion, posted under Games, Life, Thoughts » 1 comment

System GUI #7 – Pole tekstowe

2007-09-16 21:16

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:

  • Kontrolowanie, jaki fragment tekstu jest w aktualnie widoczny w ramach kontrolki. Jak wiemy, pole tekstowe powinno oferować możliwość wpisywania tekstu dłuższego (pikselowo) niż jego rozmiary, dlatego często część jego zawartości będzie niewidoczna. Należy jednak zapewnić możliwość przewijania (klawiszami strzałek wraz z Ctrl oraz Home i End), aby możliwe było ukazanie innego fragmentu tekstu.
  • Przycinanie w polu tekstowym

  • Obsługa kursora tekstowego, czyli tzw. karetki. Oprócz przesuwania za pomocą klawiatury użytkownik bardzo słusznie będzie oczekiwał, że jest to możliwe także poprzez kliknięcie myszą. Wymaga to więc istnienia sposobu na określenie, w jaki znak użytkownik trafił. A poza tym karetka musi jeszcze mrugać odpowiednio zachęcająco – w końcu dawniej nazywała się ona znakiem zachęty :)
  • Pole tekstowe z kursorem (karetką)

  • Możliwość zaznaczania tekstu. Tradycyjnie jest to możliwe poprzez przeciąganie, jak również z klawiatury (z użyciem klawisza Shift). Kontrola stanu zaznaczenia to generalnie niezbyt złożona, ale też nie tak trywialna maszyna stanów
  • Zaznaczenie w polu tekstowym

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 :]

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

Przeciążanie destruktorów

2007-09-15 11:41

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:

  1. try
  2. {
  3.    CTransaction ts;
  4.    ts.DoSomething();  // jakaś operacja
  5.  
  6.    // ...
  7.    if (SomethingBadHappened())
  8.       throw std::exception("Failure!");
  9. }
  10. catch (...) { /* ... */ }

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:

  1. class CTransaction
  2. {
  3.    public:
  4.       CTransaction() { Begin(); }
  5.       ~CTransaction() { Commit(); }
  6.       ~CTransaction(const std::exception&) { Rollback(); }
  7. };

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):

  1. CTransaction::~CTransaction()
  2. {
  3.    if (std::uncaught_exception())
  4.       // destruktor wywołany przez wyjątek - odwracamy transakcję
  5.       Rollback();
  6.    else
  7.       // normalne wywołanie destruktora - zatwierdzamy
  8.       Commit();
  9. }

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.

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

Święto programistów

2007-09-13 15:55

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ą? :)

Code monkeyNiezależ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 ;]


Author: Xion, posted under Life, Programming » Comments Off on Święto programistów

Bolączki C++ #3 – RAII vs finally

2007-09-13 9:23

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.

  1. try
  2. {
  3.    // 'wskaźnik lokalny' - chroni przed wyciekiem pamięci
  4.    std::auto_ptr<CFoo> pFoo(new CFoo(...));i
  5.  
  6.    // strumień plikowy - automatycznie zamyka otwarty plik
  7.    std::fstream FS("file.txt", std::ios::out);
  8. }
  9. catch (...) { /* ... */ }

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.:

  1. import java.io.*;
  2.  
  3. try
  4. {
  5.    FileReader fr = new FileReader("file.txt");
  6.  
  7.    // (czytanie pliku)
  8. }
  9. finally { fr.close(); }

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:

  • Brak konieczności opakowywania każdego wykorzystywanego zasobu w specjalną klasę.
  • Większa przejrzystość kodu, w którym zarówno operację pozyskania, jak i zwolnienia zasobu.
  • 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 :)

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Bolączki C++ #3 – RAII vs finally

Składanie frameworka

2007-09-12 8:57

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.

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

Prawie wszystko o przycinaniu 2D

2007-09-10 23:19

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.

Część wspólna dwóch prostokątów

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:

  1. const RECT Intersection(const RECT& rc2, const RECT& rc2)
  2. {
  3.    RECT out;
  4.    out.left = max(rc1.left, rc2.left);
  5.    out.top = max(rc1.top, rc2.top);
  6.    out.right = min(rc1.right, rc2.right);
  7.    out.bottom = min(rc1.bottom, rc2.bottom);
  8.    return out;
  9. }

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:

  • ustawienia stanu renderowania D3DRS_SCISSORTESTENABLE na TRUE
  • ustawienia prostokąta przycinania za pomocą metody 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.

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Prawie wszystko o przycinaniu 2D
 


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