Posts from 2008

C#: referencje i porównywanie

2008-11-17 14:54

W C# obiekty zwykle dostępne są poprzez referencje. Zatem zmienna typu T (jeśli jest on klasą) nie zawiera samego obiektu T, lecz tylko odwołanie do niego. Porównując dwie takie zmienne przy pomocy operatora == domyślnie sprawdzamy więc, czy pokazują one na ten sam obiekt. Podobnie jest też przy użyciu domyślnej wersji metody System.Object.Equals; robi ona dokładnie to samo, co wspomniany operator. Można mądrze powiedzieć, że oba mechanizmy sprawdzają relację identyczności obiektów.

Czasami jednak chodzi nam o coś innego: chcemy sprawdzić, czy dwa obiekty są równe, np. w sensie zawartości swoich pól. Taka równość może zachodzić także wtedy, gdy obiektyte zostały stworzone zupełnie niezależnie. Znów można mądrze stwierdzić, że chcąc dokonać takiego sprawdzenia, realizujemy dla obiektów semantykę wartości. Co wtedy zrobić?… Ano przeciążyć tudzież nadpisać wspomnianą metodę Equals i/lub operator ==.
I tu zaczynają się schody, bo wcale nie jest łatwo zrobić to poprawnie, a jeszcze trudniej jest zrobić to sensownie. Wszystko zależy od tego, czy nasza klasa ma realizować wyłącznie ową nieszczęsną semantykę wartości czy też czasami będziemy jednak sprawdzać, czy dwie referencje pokazują na ten sam obiekt (a nie na dwa równe obiekty).

  • Jeśli tak (tzn. tylko sprawdzanie równości obiektów), to powinniśmy przeciążyć zarówno operator ==, jak i metodę Equals – i to przeciążyć tak, aby działały tak samo. Wtedy zwykle zajmujemy się najpierw operatorem, a później wykorzystujemy go w metodzie:
    1. struct Foo
    2. {
    3.     private int field;
    4.  
    5.     public static bool operator == (Foo a, Foo b)    { a.field == b.field; }
    6.     public override bool Equals(Object obj)
    7.         { return obj is Foo && this == (Foo)obj; }
    8. }

    W takiej sytuacji prawdopodobnie powinniśmy też posłużyć się strukturą. Najpewniej chodzi nam bowiem o typ, który ma zachowywać się jak podstawowy: czyli coś w stylu wektora, macierzy, kwaternionu, itp.

  • W przeciwnym przypadku (czyli: czasem porównujemy wartości, a czasem referencje) klasa powinna pozostać klasą, lecz należy przedefiniować jej metodę Equals. To jej powinniśmy używać, do sprawdzenia relacji równości między obiektami. Kiedy zaś chcemy zwyczajnie porównać referencje, możemy wtedy uciec się do operatora ==.

Uff, całkiem to zawiłe, prawda? Niestety nie jest łatwo uchwycić różnicę między równością a identycznością – a w językach, które obiekty realizują przez referencję sytuacja komplikuje się jeszcze bardziej. Zaś już chyba całkiem rozmywa się wtedy, gdy niektóre typy traktujemy per wartość, a niektóre per referencja…
A tak przecież jest C#. I jednocześnie podobno to jeden z prostszych języków do nauki i użytkowania :D

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

Grzechy grafiki

2008-11-14 22:22

Karty graficzne mogą podołać renderowaniu coraz większej liczby wierzchołków, więc grafika w grach jest coraz bardziej szczegółowa – to oczywiste. Niezbyt oczywiste jest jednak to, czy jest ona faktycznie lepsza, bowiem nawet mając bardzo dobrze zrobione modele i tekstury niekoniecznie można złożyć je w przyzwoicie wyglądający świat.
Według mnie jest kilka głównych grzechów, jakie można przy tej okazji popełnić:


  • Model postaci ładny,
    ale co to za teren?

    Syndrom pustyni z drzewkami. Nazywam w ten sposób wrażenie, że na scenie obiekty niespecjalnie pasują do siebie. Przykładem czegoś takiego jest heightmapa pokryta niezbyt urozmaiconą teksturą (stąd ‘pustynia’), do której “przyklejone” są wyraźnie odcinające się drzewa. Taki teren jest jak najbardziej poprawny w demie silnika, ale w grze oczekiwalibyśmy trochę więcej urozmaicenia i trochę więcej spójności. Może jakieś trawy i krzaczki? ;)

  • Ocean w kałuży. Woda to okazja do wielu ciekawych efektów, z refleksami światła i odbiciami na czele. Jednak inaczej wygląda duży zbiornik wodny, a inaczej małe jeziorko. Dlaczego więc tak często widzimy oceany płaskie jak stół i stawy, w których wręcz szaleją sztormy? Czyżby tak trudno było dobrać wagi dla używanej w vertex shaderze funkcji sinus? :)

  • Uwaga na słońce! ;P

    Za dużo blooma. Odpowiednie efekty typu post-process to, oprócz upiększania sceny, także dobry sposób na to, by przyciągnąć uwagę do wybranych miejsc (w domyśle: tych lepiej zrobionych niż inne). Jeśli jednak przesadzi się z bloomem, lens-flare i innymi pseudorealistycznymi upiększaczami, to całość robi się mniej imponująca, a bardziej męcząca.

To na pewno nie wszystkie błędy, które dają się w miarę łatwo zauważyć. Wydaje mi się jednak, że te należą do jednych z łatwiejszych do popełnienia. W końcu skoro już napisaliśmy jakiś fajny shader, to warto go wcisnąć, gdzie się da, prawda? :) No cóż, nie zawsze jest to dobry kierunek postępowania…

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

Trzy kroki do użycia biblioteki

2008-11-10 20:03

Żeby w C++ użyć w naszym programie kodu pochodzącego ‘z zewnątrz’ – a więc jakiejś biblioteki – trzeba się trochę napocić. Musimy bowiem odpowiednio “nakarmić” informacjami zarówno kompilator, jak i linker, co czasami skutkuje tym, że konieczne są w sumie trzy kroki. Są to:

  1. Dodanie odpowiedniej biblioteki statycznej (.lib) do wejścia linkera. W Visual C++ jest to uzupełnienie pola Additional Dependencies w zakładce Linker > Input we właściwościach projektu (można to także osiągnąć dyrektywami #pragma bezpośrednio w kodzie).
  2. Dołączenie odpowiednich plików nagłówkowych do naszego kodu – oczywiście przy pomocy dyrektywy #include. To ukłon w stronę kompilatora, aby mógł on poprawnie zidentyfikować wszystkie nazwy (funkcje, klasy, itp.) pochodzące z biblioteki.
  3. Odpowiednie potraktowanie ewentualnych przestrzeni nazw używanych przez kod zewnętrzny, czyli pisanie ich przed każdą nazwą (np. std::list zamiast list) lub skorzystanie z dyrektyw using.

To w sumie całkiem sporo pracy, wynikającej z niezbyt zautomatyzowanego sposobu budowania programów. W innych językach (głównie w tych, których pojęcie ‘projektu’ jest częścią ich samych, a nie tylko IDE) drugi z tych kroków często nie występuje w ogóle, bo został całkowicie zintegrowany z trzecim. Zawsze jednak trzeba wyraźnie wskazać, gdzie znajduje się kod biblioteczny, który ma zostać dołączony do naszego skompilowanego programu – czyli wykonać krok pierwszy. Ta czynność, często zapominana (bo zwykle nie znajdująca odzwierciedlenia w samym kodzie) jest bowiem najważniejsza.

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

Kolejność na liście inicjalizacynej

2008-11-07 10:40

Jak wiadomo, do konstruktorów w C++ możemy doczepić listy inicjalizacyjne, pozwalające nadawać początkowe wartości polom macierzystej klasy (i nie tylko zresztą). Związany jest z tym pewien pozornie zaskakujący fakt, dotyczący kolejności inicjalizacji tych pól:

  1. class Foo
  2. {
  3.     private: string a, b;
  4.     public: Foo() : b("b"), a("a") { }
  5. };

Są one zawsze ustawiane w porządku zgodnym z ich deklaracjami w klasie. Tak więc powyżej inicjalizowane będzie najpierw pole a, zaś potem b – mimo że w konstruktorze kolejność jest odwrotna. Oczywiście w takim prostym przypadku nie ma to znaczenia, ale jeśli między polami występują zależności, wtedy może to być przyczyną “dziwnych” błędów. Porządniejsze kompilatory ostrzegają na szczęście przed rozbieżnymi kolejnościami pól w klasie i na liście w konstruktorze.

Można naturalnie zapytać, dlaczego w ogóle jest to zorganizowane tak nieintuicyjnie. Pierwsza nasuwająca się odpowiedź – “bo to C++” – nie jest bynajmniej zadowalająca :) Tak naprawdę przyczyną jest to, iż konstruktorów może być naturalnie więcej niż jeden:

  1. public:
  2.     Foo() : b("b"), a("a") { }
  3.     Foo(const Foo& foo) : a(foo.a), b(foo.b) { }

i mogą mieć one różną kolejność pozycji na liście inicjalizacyjnej. Gdyby to ona liczyła się bardziej, wtedy różne obiekty tej samej klasy byłyby konstruowane na różne sposóby. Co gorsza, sposób tej konstrukcji (kolejność inicjalizacji pól) musiałby zostać gdzieś zapamiętany na czas życia obiektu, jako że jego pola powinny być później zniszczone w kolejności odwrotnej do konstrukcji – zgodnie z zasadami języka.
Krótko mówiąc, zastosowanie “intuicyjnego” podejścia skutkowałoby nie tylko znacznie większymi niejasnościami, ale i dodatkowymi kłopotami zarówno dla programisty, jak i kompilatora.

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Kolejność na liście inicjalizacynej

Engine jest wielki, a ja malutki

2008-10-31 18:03

Zapewne niespotykana często koniunkcja planet sprawiła, że na forum Warsztatu pojawiło się ostatnio kilka przypadków dość specyficznych pytań. Ich autorzy chcieli dowiedzieć się, czy – najogólniej mówiąc – są w stanie coś konkretnego w dziedzinie programowania osiągnąć. “Czy dam radę napisać taką-a-taką grę?” jest przykładem takiego właśnie pytania.

Skrajnie zresztą dziwnego, moim skromnym zdaniem. Nie chodzi tu nawet o fakt, że odpowiedź na podobne pytanie jest niemożliwa do udzielenia. Bardziej zdumiewa mnie to, iż obecni adepci sztuki koderskiej jakoś lubią wynajdywać sobie różne potencjalne przeszkody. A może za mało wiem o programowaniu? Może za słabo znam matematykę? Albo jestem po prostu za młody?… Takie i podobne “argumenty” są bowiem przytaczane przy okazji wspomnianych pytań.
Jest dla mnie dosyć zaskakujące, że dzisiejsi ‘młodzi’ (raczej w sensie umiejętności) tak szybko się “starzeją”. Jeszcze całkiem nieźle pamiętam, że będąc początkującym prawie w ogóle nie przejmowałem się realnością podejmowanych amatorskich projektów. Gra 3D, strategiczna czy sieciowa (MMORPG-ów raczej wtedy nie było) z innowacyjnym gameplayem i nowatorskimi rozwiązaniami technicznymi? Ależ oczywiście, toż to praca najwyżej na tydzień ;D Jak się kończyły takie zamiary, nietrudno zgadnąć. Ani mi, ani podobnym do mnie ‘kolegom po fachu’ nie przeszkadzało to jednak próbować.

Co więc zmieniło się, że teraz widzę coraz więcej nowicjuszy pełnych wątpliwości? Czy chociażby programowanie gier stało się trudniejsze? Powiedziałbym raczej, że wręcz przeciwnie (weźmy np. coraz więcej gotowych silników i coraz lepsze wersje graficznych API), a trend ten wydaje się przy tym stabilny. Ale nawet gdyby tak nie było, to jaki sens zadawanie niedorzecznych pytań w stylu “Czy mi się uda?”. Nie, nie uda ci się – to mogę ci powiedzieć w ciemno. Z każdego nieudanego projektu można jednak wyciągnąć wnioski i nauczyć się pożytecznych rzeczy. I dlatego nie ma co przerażać się, że to trudne, niezrozumiałe czy wymagające napisania wielkich ilości świetnie działającego kodu. Wszystko przecież przychodzi z czasem, a porażki są nieodłączną częścią postępów w nauce.
Zatem – motyki w garść, Słońce czeka ;)

Więcej Schowka

2008-10-26 15:03

Wiele różnych rozbudowanych programów – jak np. IDE lub pakiety biurowe – zawiera mechanizmy rozszerzające funkcjonalność windowsowego Schowka. Oznacza to najczęściej możliwość zachowania więcej niż jednego wycinka naraz i przełączanie się między nimi, gdy chcemy wkleić gdzieś któryś z nich.
Dotąd nie byłem zbyt entuzjastycznie nastawiony do takiej funkcjonalności – może dlatego, że posiadanie więcej niż jednego Schowka było poza zasięgiem moich możliwości pojmowania ;) Zdarzyło się jednak, że kilka razy potrzebowałem wrócić do tekstu, który już “nieopatrznie” nadpisałem kilkoma kolejnymi skopiowaniami do Schowka. Wtedy też zacząłem rozglądać się za aplikacją, która w jakiś sensowny sposób rozszerza możliwości systemowego Schowka, dodając do niego więcej ‘slotów’.

Poszukiwania nie były niestety specjalnie owocne. Najlepsze, co udało mi się znaleźć, to program o wielce oryginalnej nazwie MultiClipBoard. Narzędzie to chowa się do systemowego zasobnika i pozwala posiadać nieograniczoną liczbę niezależnych Schowków na wycinaną treść. Potrafi też tworzyć nowy przy każdym kopiowaniu, dzięki czemu niczego nie zgubimy. Niestety, nie oferuje zbyt wielu przydatnych skrótów klawiszowych, umożliwiających chociażby cofnięcie się do poprzednio wyciętego elementu.
Ale oczywiście lepsze to niż nic – przynajmniej na razie. Bo kto wie, może w Windows 7 zamiast kolejnych wodotrysków zobaczymy coś użytecznego, jak właśnie nieco bardziej inteligentną obsługę Schowka. Chociaż to pewnie tylko pobożne życzenia :)

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

O kompatybilności wstecz

2008-10-20 9:47

Użytkownicy zawsze lubią, gdy nowe wersje programów współpracują ze wszystkim, z czym współpracowały stare wersje. Niemal każdy system operacyjny zachowuje na przykład kompatybilność binarną w swoich kolejnych edycjach. Dzięki niej programy napisane dla poprzednich wersji systemu mogą działać i z nowszymi wersjami.

W programowaniu pewnym odpowiednikiem takiego zachowania jest kompatybilność źródeł. Objawia się ona tym, że nowa wersja biblioteka daje się używać bez modyfikacji źródeł programu, który korzysta ze starej wersji. Dla jego autora może być to początkowo wygodne, ale na dłuższą metę powoduje też pewne problemy.
Zachowanie zgodności ogranicza bowiem możliwość zmian w interfejsie biblioteki, pozwalając głównie na dodawanie nowych funkcji, klas, metod, itp. To właśnie tutaj leży przyczyna istnienia paru małych absurdów Windows API, jak choćby funkcje z przyrostkiem Ex czy obecne prawie w każdej strukturze pole cbSize, które należy ustawić na jej wielkość.

Usilnie utrzymywana kompatybilność nie pozwala też naprawić ewentualnych błędnych decyzji projektowych. Dlatego czasami warto się od niej uwolnić; choć jest to najpierw bolesne, korzyści zwykle przewyższają koszty. Wystarczy spojrzeć na przykład na DirectX 10, a już zwłaszcza na całą platformę .NET.

Tags:
Author: Xion, posted under Computer Science & IT, Thoughts » 2 comments
 


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