Monthly archive for July, 2009

PlaySound bez ścięć

2009-07-31 17:03

Chcąc w prosty sposób odtworzyć dźwięk w programie pod Windows, możemy skorzystać z funkcji WinAPI o nazwie PlaySound. Wystarczy podać jej nazwę pliku dźwiękowego, by ten został odegrany:

  1. PlaySound (TEXT("C:\\Windows\\Media\\ding.wav"), NULL, 0);

Jak to jednak bywa w przypadku metod najprostszych, nie jest to rozwiązanie doskonałe. Jedną z jego wad jest bowiem to, że próbka dźwiękowa jest tutaj wczytywana z dysku tuż przed jej pierwszym odtworzeniem. W większości przypadków trwa to chwilę, którą da się zauważyć jako krótkie opóźnienie przed usłyszeniem dźwięku.
W grach (i nie tylko) jest to oczywiście niedopuszczalne, więc zwykle najlepszym wyjściem jest użycie wyspecjalizowanych bibliotek dźwiękowych. Jeśli jednak nie chcemy dołączać dodatkowych plików DLL lub wykorzystywać DirectX-a, możemy temu opóźnieniu zaradzić.

Funkcja PlaySound pozwala bowiem na odtworzenie dźwięku nie tylko z pliku, ale też z pamięci operacyjnej:

  1. PlaySound ((LPCTSTR)pSound, NULL, SND_MEMORY);

W tym celu posłużyć się trzeba flagą SND_MEMORY i podać wskaźnik do bloku pamięci, w którym znajduje się nasza próbką dźwiękowa. Skąd go wziąć? Ano chociażby wczytać plik dźwiękowy z dysku do pamięci:

  1. const void* LoadSound(const TCHAR* fileName)
  2. {
  3.     // uwaga: brak obsługi błędów
  4.     HANDLE file = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ,
  5.                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  6.     DWORD size = GetFileSize(file, NULL);    // max. 2GB
  7.  
  8.     char* sample = new char [size];
  9.     char* p = sample;
  10.     DWORD bytesRead;
  11.     do
  12.     {
  13.         ReadFile (file, p, size, &bytesRead, NULL);
  14.         p += bytesRead;
  15.     } while (bytesRead > 0);
  16.  
  17.     CloseHandle (file);
  18.     return sample;
  19. }

“Trik” polega na tym, że operację wczytywania możemy przeprowadzić wcześniej, a więc w trakcie ładowania całej gry, pojedynczego etapu, pokoju, krainy, itp. – czyli wtedy, gdy jej potencjalnie długi czas nie będzie nikomu przeszkadzał. Potem zaś da się ją odtworzyć z pamięci i nie doświadczać żadnych opóźnień.
Trzeba tylko pamiętać, żeby na koniec zwolnić pamięć przeznaczoną na wczytaną próbkę.

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

Mało znane tagi HTML

2009-07-25 18:40

Prawie każdy potrafi sklecić prostą stronę WWW, nie mówiąc już o napisaniu nieskomplikowanego tekstu w formacie HTML. Do tego celu wystarczy zwykle użyć od kilku do kilkunastu standardowych tagów, odpowiedzialnych za podstawowe formatowanie (pogrubienie, kursywa, itp.) oraz wstawianie obiektów w rodzaju obrazków czy tabel.
W rzeczywistości istnieje jednak znacznie więcej znaczników, z których wiele jest bardzo mało znanych. Oto kilka spośród nich:

  • <abbr> i <acronym> służą do wstawiania akronimów (odpowiednio: literowych i głoskowych) wraz z rozwinięciem (podawanym przy pomocy atrybutu title). Przykładami mogą być chociażby NATO czy PZU. W większości przeglądarek owo rozwinięcie pojawi się po najechaniu kursorem na dany akronim.
  • <dl> pozwala na stworzenie tzw. listy definicyjnej, czyli krótkiego słownika pojęć. Każdy termin należy w niej umieścić wewnątrz tagu <dt>, zaś jego rozwinięcie w tagu <dd>. W wyniku możemy otrzymać coś takiego:
    jeden
    liczba o jeden mniejsza niż dwa
    dwa
    liczba o jeden większa niż jeden
  • Tagi <em>, <code>, <kbd> oraz kilka podobnych to próba dodania do HTML sposobu na określenie znaczenia (a nie tylko formatowania) poszczególnych fragmentów dokumentu. W jaki sposób te tagi są interpretowane przez wyszukiwarki pozostaje raczej niejasne, ale można podejrzewać, że większość nie przywiązuje do nich dużej wagi, bo ich używanie na stronach nie jest specjalnie częste. Jeśli jednak zdecydujemy się na konsekwentne korzystanie z nich, to zaletą takiego postępowania będzie łatwość sformatowania np. wszystkich kawałków kodu na naszej stronie przy pomocy odpowiedniego stylu zaaplikowanego do znaczników <code>. Trudno aczkolwiek nie zauważyć, że klasy w CSS działają właściwie tak samo…
  • Analogicznie tagi <ins> i <del> mogą służyć do oznaczania zmian w dokumencie: tekstu wstawionego lub usuniętego, łącznie z możliwością podania daty dokonania zmiany. Żadna z głównych przeglądarek nie obsługuje jednak tego feature‘u, więc póki co przydatność tych tagów jest bliska zeru.

Oczywiście lista ta jest daleka od bycia kompletną, bo zasób wszystkich znaczników HTML liczy sobie dobrych kilkadziesiąt pozycji. Warto go przejrzeć, bo istnieje duża szansa, że natrafimy na coś przydatnego :)

Tags:
Author: Xion, posted under Internet » 8 comments

Badania terenowe nad budowaniem stringów

2009-07-18 13:04

Cokolwiek kodujemy, konieczność zbudowania dużego łańcucha znaków składającego się z kolejno dołączanych fragmentów pojawia się bardzo często. To może być zapytanie do bazy danych, misternie wyrzeźbiony adres URL, dynamicznie generowany i kompilowany później shader czy w końcu obszerny i szczegółowy komunikat o błędzie.
Wspólną cechą tych tekstów jest to, że budujemy je stopniowo, kawałek po kawałku. Na takie okazje niektóre języki (jak .NET-owe lub Java) posiadają specjalne klasy w rodzaju StringBuilder. Mają one być efektywniejsze niż bezpośrednie używanie typów string, głównie ze względu na nietworzenie wielu łańcuchów i niezaśmiecanie nimi pamięci, co odciąża garbage collector z dodatkowej pracy.

W C++ nie ma oczywiście odśmiecacza, który musiałby po nas sprzątać, i nie ma też narzędzi typu StringBuilder. Czy to oznacza więc, że możemy radośnie korzystać z samej klasy std::string do budowania dowolnie długich tekstów, bo żadnej efektywniejszej alternatywy nie ma?… To właśnie zdecydowałem się sprawdzić, przeprowadzając mały eksperyment z tworzeniem długich łańcuchów znaków kilkoma sposobami, aby potomni nie musieli rozwiązywać tego jakże uciążliwego dylematu ;]
Przechodząc do rzeczy, wpierw wysmażyłem taką oto funkcję która generuje tekst o podanej minimalnej długości:

  1. const string GenerateString(string::size_type minLen)
  2. {
  3.     string res;
  4.     string::size_type currLen = 0;
  5.  
  6.     while (currLen < minLen)
  7.     {
  8.         string::size_type next = rand() % (MAX_FRAG_LEN + 1);
  9.         res += STRINGS&#91;next];
  10.         currLen += next;
  11.     }
  12.  
  13.     return res;
  14. }&#91;/cpp]
  15. Użyta w niej tablica <code>STRINGS</code> zawierała przygotowane wcześniej "kawałki" w postaci losowych napisów, z których składany był wynikowy tekst. Każdy element <code>STRINGS[n]</code> był tu tekstem o długości <code>n</code> znaków (z zakresu 0 do 111).
  16. Powyższej funkcji użyłem następnie do wygenerowania stringów o długościach 222.222, 555.555, 1.111.111, 5.555.555 i 11.111.111 znaków, mierząc czas każdej z tych operacji i uśredniając go z 10 powtórzeń. Skąd takie długości tych tekstów?... No cóż, tylko co najmniej takie wartości dawały sensowne i wystarczają długie czasy, które dawało się zmierzyć i porównać z innymi :)
  17.  
  18. No właśnie - a jakież to inne sposoby na konstruowanie napisów można było jeszcze wymyślić? Otóż wymyśliłem jeszcze dwa, polegające na wykorzystaniu wektora znaków (<code>vector&lt;char&gt;</code>). Operacja konkatenacji została w nich zaimplementowana jako rozszerzenie wektora (metoda <code>resize</code>), a następnie przekopiowanie zawartości stringa w powstałe miejsce - czyli mniej więcej tak:
  19. [cpp]// res to vector<char>
  20. res.resize (currLen + next);
  21. memcpy (&res[currLen], STRINGS[next].data(), next * sizeof(char));

Wyróżnienie dwóch sposobów polegało natomiast na tym, że w jednym z nich dodatkowo wywoływałem na samym początku metodę reserve w celu uprzedniej rezerwacji pamięci w wektorze na 3 * minLen / 2 elementów.

Testy przeprowadziłem na standardowej implementacji klas string i vector dostępnej w Visual C++ 2005. Wyniki eksperymentu przedstawiają się następująco:

Można w nich przede wszystkim zauważyć, że zarządzanie pamięcią w klasie string jest domyślnie znacząco lepsze niż w vector. Najszybszą metodą konstruowania napisów okazuje się jednak użycie wektora z uprzednią rezerwacją pamięci, która okazuje się o ok. 20% szybsza niż zwykła konkatenacja stringów.

Czy to oznacza, że powinniśmy stworzyć sobie własną klasę StringBuilder, opierającą się na wektorze właśnie, i jej używać? Niekoniecznie. Jak widać, znaczące różnice pojawiają się dopiero przy tworzeniu napisów o wielkościach rzędu megabajta lub więcej. Konia z rzędem temu, kto tworzy tak duże zapytania lub shadery :) Do większości typowych zastosowań bezpośrednie użycie typu string wydaje się więc wystarczające.
No chyba że istnieją bardziej efektywne sposoby na budowanie stringów w C++, których nie udało mi się wymyślić – co jest swoją drogą całkiem prawdopodobne :)

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

Dziel i rządź… wątki

2009-07-15 11:35

Na forum Warsztatu – i pewnie na wielu innych forach tego typu – dość często występuje zjawisko, którego przyczyn ciężko mi jest dociec i równie ciężko jest je zrozumieć. Powoduje ono przy tym trochę dodatkowej pracy dla moderatorów, czyli między innymi także i dla mnie.

O co chodzi? O dziwną praktykę “doklejania” swoich pytań do już istniejących wątków. Zazwyczaj zaczyna się od tego, że ktoś przegląda sobie temat, na który udzielono już zadowalających autora odpowiedz. I wtedy pewnie ów ktoś przypomina sobie, że przecież miał/ma podobny problem… Względnie przeczytał cały ten wątek z nadzieją na uzyskanie rozwiązania swojego problemu (bo przecież ma podobny), która to jednak okazała się płonna.
Co wtedy robi ów jegomość? Ano chwyta za wirtualne pióro, żeby podzielić się ze światem swoimi programistycznymi troskami. I nie wiedzieć czemu w większości przypadków robi to, dopisując po prostu nowy post w tym samym wątku. Dlaczego? – to właśnie pytanie od dawna spędza mi sen z powiek…

Dobra, powiedzmy, że trochę koloryzuję ;] Tak naprawdę ludzie zazwyczaj uzasadniają, dlaczego zamiast założenia nowego tematu, dopisują swoje pytania w już istniejącym (co swoją drogą jest wskazówką, iż mają świadomość, że postępują źle). Typowe są tu mianowicie dwa wyjaśnienia.
Po pierwsze: z lenistwa. To paradoksalnie jest ten lepszy powód, a przynajmniej dający się zrozumieć (w końcu teoretycznie założenie nowego wątku to aż kilka kliknięć więcej…). Dodatkowo można też docenić tutaj delikwenta za świadomość posiadania określonych przywar charakteru i gotowością do przyznania się do nich w razie potrzeby. Innymi słowy, wykazuje się on wtedy wielce pożądaną, acz coraz rzadszą w dzisiejszych czasach cnotą szczerości… Tak, w tym przypadku żartuję oczywiście ;P

A jaki jest ten drugi powód? Otóż brzmi on następująco: “Nie zakładam nowego wątku, ażeby nie zaśmiecać forum”. To, jaka pokrętna logika stoi za takim stwierdzeniem, jest dla mnie nierozwiązywalną zagadką. O ile łatwiej byłoby później znaleźć pytanie (i odpowiedź) stanowiące osobny wątek, opatrzony – miejmy nadzieję – adekwatnym tytułem, umieszczony w odpowiednim dziale niż któryś z kolei post numer N na stronie M wątku X, który to oryginalnie dotyczył czegoś innego.
Trzeba też wspomnieć o tym, że podczepianie się pod istniejący temat to zwykła kradzież. Kradnie się bowiem część czasu i uwagi, którą inni forumowicze poświęcili oryginalnemu pytaniu i “przekierowuje” je na nowy problem. Jednocześnie ten pierwotny temat traci szansę na dalsze rozwinięcie – chyba że wciąż będą się pojawiać do niego odpowiedzi i przeplatać z tymi odnoszącymi się do nowego pytania. W rezultacie mamy śmietnik – a podobno tego właśnie chcieliśmy uniknąć.

W regulaminie forum Warsztatu znajduje się stosowny punkt, odradzający tego typu praktyk. A mimo to moderatorzy nie walą za nie po łapach banami, nie kasują takich wtrąconych pytań, tylko pracowicie dzielą te wątki na dwie części – bynajmniej nie usuwając tych nowych. I jak tu teraz mówić, że jesteśmy surowi i źli? ;)

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

LINQ bywa przydatny

2009-07-09 19:35

Wśród feature‘ów wprowadzonych w wersji 3.5 frameworka .NET jest między innymi LINQ (Language INtegrated Query). Mechanizm ten umożliwia – w dużym skrócie rzecz jasna – konstruowanie zapytań odnoszących się do kolekcji obiektów (w zasadzie dowolnego rodzaju) przy pomocy operatorów znanych z relacyjnych baz danych, jak SELECT czy WHERE. Ponadto w .NET 3.5 język C# został też odpowiednio rozszerzony, aby zapytania ta składniowo mogły przypominać kwerendy podobne do tych występujących w różnych odmianach języka SQL.

“A po co to komu?”, można zapytać od razu i nie ukrywam, że moja reakcja na LINQ była podobna. W końcu kto by chciał zaśmiecać taki ładny język jak C# zupełnie nieprzydatnym nikomu SQL-em? ;] Poza tym mówimy tutaj o czymś trochę z innej bajki, bowiem języki zapytań różnią się od języków programowania tym, że są deklaratywne: pisanie w nich polega na określeniu, co chcemy otrzymać – nie zaś tego, co po kolei program ma robić. (Jak nietrudno się domyślić, ogranicza to trochę stosowalność takich języków). Czemu więc chce się te dwie rzeczy połączyć?…

Odpowiedź jest jak zwykle dość prosta: dla wygody. Za pomocą LINQ nie zrobi się niczego nowego, ale pewne operacje można wykonywać prościej. Czasami można sobie oszczędzić dodatkowych pętli i zmiennych lokalnych – jak chociażby w poniższym przykładzie, w którym chcemy pobrać wszystkie wartości zaznaczone na liście z polami wyboru (check box list) i coś z nimi zrobić:

  1. // wersja z pętlą
  2. List<int> selected = new List<int>();
  3. foreach (ListItem item in checkBoxList.Items)
  4.     if (item.Selected) selected.Add (Convert.ToInt32(item.Value));
  5. DoSomething (selected);
  6.  
  7. // wersja LINQ
  8. DoSomething (from ListItem item in checkBoxList.Items
  9.              where item.Selected
  10.              select Convert.ToInt32(item.Value));

Można się oczywiście spierać, która wersja jest czytelniejsza i którą jest łatwiej/szybciej napisać. To jednak jest w dużym stopniu kwestią subiektywną. Jak dla mnie możliwość uniknięcia kolejnej pętli, która udaje, że coś robi (a tak naprawdę tylko przerzuca dane z jednego miejsca w inne) jest całkowicie zadowalająca.
Przy bardziej skomplikowanych przypadkach miałbym aczkolwiek dużo większe możliwości. Jest oczywiście możliwe pisanie błyskotliwych kwerend przemiatających po cztery pojemniki naraz i używających takich operatorów jak group czy orderby, by zrobić w trzech linijkach zapytania to, co inaczej wymagałoby 20 wierszy kodu. Sęk w tym, że prawdopodobnie odczytanie znaczenia tych 20 wierszy byłoby wówczas znacząco łatwiejsze ;P

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

Sortowanie dla zaawansowanych

2009-07-02 18:06

Gdy trzeba posortować jakąś kolekcję w sposób – nazwijmy to – standardowy, to zwykle nie ma z tym problemu. Chyba każdy język posiada coś na takie okazje: mamy funkcję qsort, std::sort (i std::list::sort), System.Array.Sort, java.util.Arrays.sort, i tak dalej. Zwykle wystarczy podać im tablicę lub kolekcję i już mamy ją posortowaną.
Istnieją jednak przynajmniej dwie sytuacje, gdzie taka standardowa procedura nie jest wystarczająca. Jest tak na przykład wtedy, gdy:

  • zwykły sposób porównywania obiektów nie jest tym, którego chcielibyśmy używać przy sortowaniu. Ów “zwykły sposób” jest wyznaczany najczęściej przez operatory relacyjne (<, >=, itp.), które nie muszą być zdefiniowane (np. jeśli sortujemy obiekty własnego typu złożonego) lub mogą być określone inaczej niż byśmy w danym przypadku sortowania chcieli.
  • życzymy sobie odwrotnego porządku sortowania, czyli np. malejącego zamiast domyślnego rosnącego

W powyższych przypadkach rozwiązanie sprowadza się zwykle do zdefiniowania własnego kryterium porównawczego obiektów, które sortujemy. Spotyka się tu najczęściej dwie formy takiego kryterium:

  • W C++ możemy zdefiniować predykat binarny (funkcję przyjmującą dwa argumenty i zwracającą wartość binarny), który określa dla obiektów tzw. ścisłe uporządkowanie słabe (strict weak ordering). Mówiąc w skrócie, funkcja ta powinna mieć identyczne właściwości jak zwykły operator mniejszości <, czyli zwracać true, jeśli pierwszy obiekt jest “mniejszy” niż drugi. Jak nietrudno zauważyć, taka informacja jest wystarczająca do jednoznacznego uporządkowania dwóch obiektów. W szczególności a == b zachodzi wtedy i tylko wtedy, gdy !(a < b) && !(b < a), zatem taki predykat może też służyć do sprawdzania, czy dwa obiekty są sobie równe.
    Domyślnym predykatem sortowania w C++ jest std::less, który działa identycznie jak operator <. std::greater pozwala natomiast na odwrócenie porządku sortowania.
  • Wynik Relacja
    < 0 x < y
    == 0 x == y
    > 0 x > y

    W C, C# i w Javie korzysta się z kolei z funkcji porządkujących, które przyjmują dwa obiekty i zwracają w wyniku liczbę całkowitą. Liczba ta określa jednoznacznie uporządkowanie tych dwóch obiektów: czy jeden jest mniejszy niż drugi, równy mu czy większy od niego - tak, jak to pokazano w tabelce obok.
    Interesującą cechą takiego sposobu porządkowania jest to, że tak naprawdę używanie jakichkolwiek porównań (<=, > itp.) często nie jest w nim potrzebne. Zazwyczaj bowiem ową funkcję porównującą można zaimplementować następująco:

    1. int compare(x, y) { return val(x) - val(y); }

    gdzie val(x) oznacza tutaj pewną wartość liczbową przyporządkowaną obiektowi x, określającą jego miejsce w danym porządku sortowania. Wartość ta powinna być tak dobrana, aby posortowanie wszystkich takich liczb rosnąco dało nam w wyniku pożądaną kolejność uporządkowania naszych obiektów. Jeśli więc, przykładowo, chcemy posortować ciągi znaków w C# tak, by najdłuższe znalazły się na początku (czyli malejąco względem długości), to val(x) oznaczać tu będzie -x.Length().

Uff, jak widać sortowanie czasami nie jest wcale taką prostą sprawą :) Cieszmy się jednak, że nawet w bardziej skomplikowanych sytuacjach do napisania pozostają nam jedynie proste predykaty porównujące, nie zaś... całe algorytmy ;]

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


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