Rozszerzalność od dawna jest w modzie: praktycznie żadna poważniejsza aplikacja nie obywa się bez jakiegoś systemu pluginów, czyli “wtyczek” zwiększających jej funkcjonalność. Niektóre robią to przy okazji (acz ze słusznych powodów), inne czynią z elastyczności i rozszerzalności swój główny oręż (patrz np. uniwersalne komunikatory w typie Mirandy). Wszystko to kojarzy się trochę linuksiarsko, jednakże obecnie także wiele programów stricte pod Windows zachęca (a przynajmniej umożliwia) swoich użytkownika do zakasania rękawów i zakodowania im nowych funkcji.
Dotyczy to także aplikacji na platformę .NET. Co więcej, sama jej natura ułatwia budowanie programów wspierających koncepcję rozszerzalności. Rzecz opiera się na pojęciu assembly, czyli czegoś w rodzaju pakietu (javowe skojarzenia wskazane) zawierającego kod, a więc klasy. Ze względu na to, że .NET Framework zawiera wbudowany kompilator, jest zupełnie możliwe, by nasz program udostępniał całe środowisko do pisania do niego pluginów, a następnie przerabiania ich na kod wykonywalny i podłączania ich do aplikacji. Nie twierdzę, że znam program, który rzeczywiście tak robi, ale przynajmniej w teorii jest to możliwe :)
Powszechniejsze wydaje mi się prostsze podejście, w którym assembly dostarcza się w postaci skompilowanej. Zazwyczaj jest to biblioteka .NET-owych, zapisana jako plik .dll, niemający przy tym zbyt wiele wspólnego z natywnymi czy COM-owymi DLL-ami. Wczytanie go jest bardzo proste, gdy wykorzystujemy do tego klasę System.Reflection.Assembly
:
Wskazana jest tutaj ostrożność np. w postaci zapewnienia, że każde assembly ładujemy tylko raz. Najlepiej jest wyznaczyć osobny podkatalog na wtyczki do naszego programu i ładować je jednokrotnie, zapewne podczas uruchamiania samej aplikacji.
Gdy mamy już gotowy obiekt klasy Assembly
(niezależnie od tego, czy skompilowaliśmy go sami czy wczytaliśmy go z gotowej binarki tak, jak powyżej), chcielibyśmy pewnie jakoś wykorzystać zawarty w nim kod. Są nim oczywiście jakieś klasy, które możemy pobrać metodą GetExportedTypes
. Zwróci nam ona metody tablicę obiektów Type
, czyli znanej zapewne metaklasy należącej do .NET-owego systemu refleksji. Z nimi zaś zrobić możemy… no, prawie wszystko :) Do interesujących w kontekście pluginów czynności należy przede wszystkim sprawdzenie, czy dana klasa implementuje jakiś ustalony przez nas interfejs “wtyczkowy”, a następnie utworzenie jej obiektu:
Takie beztroskie, dynamiczne tworzenie obiektów z binarnego kodu wczytanego już w trakcie działania programu jest jak najbardziej możliwe i, jak widać wyżej, całkiem proste – stosujemy do tego metodę CreateInstance
klasy o wdzięcznej nazwie Activator
. Wymogiem jest obecność w klasie wtyczki odpowiedniego konstruktora. W powyższym kodzie zakładamy na przykład najprostszy przypadek, iż dostępna jest jego wersja bezparametrowa.
To w sumie wszystko, jeśli chodzi o wczytywanie. To, w jaki sposób plugin może rozszerzać możliwości naszej aplikacji, zależy głównie od zawartości interfejsu, który nazwałem tutaj umownie IPlugin
. Projektując go, powinno się zadbać o jego elastyczność i prostotę, a także wziąć pod uwagę chociażby kwestie bezpieczeństwa.
Obecne temperatury są uciążliwe nie tylko dla nas – organizmów opartych o białka i węgiel, ale też dla naszych półprzewodnikowych, krzemowych gadżetów. Dotyczy to również komputerów oraz ich procesorów. Jak każde układy scalone, mają one tendencję do nagrzewania się – czasem nadmiernego. Dlatego dobrze jest monitorować temperaturę procesora.
Istnieje oczywiście wiele gotowych aplikacji, które na to pozwalają, ale przecież zawsze fajniej jest zakodować coś samemu, nieprawdaż? :) A już zupełnie świetnie jest wtedy, gdy użyjemy do tego naszego ulubionego narzędzia do administrowania systemem, czyli PowerShella.
Przy jego pomocy pobranie aktualnej temperatury procesora nie jest trudne. Posługujemy się do tego WMI – Windows Management Instrumentation – które to jest rozszerzeniem modelu sterowników systemu Windows, pozwalającym m.in. na monitorowanie różnego rodzaju informacji diagnostycznych. PowerShell zawiera wbudowane komendy, pozwalające na tworzenie obiektów WMI – wśród nich również takich, które udostępniają informacje na temat temperatury procesora:
Z bliżej nieokreślonych powodów wartość temperatury podawana jest w dziesiątych częściach kelwinów, więc konieczne jest ich przeliczenie na stopnie Celsjusza. Ponadto istnieje też prawdopodobieństwo, że używana tutaj klasa WMI o nazwie MSAcpi_ThermalZoneTemperature
nie jest dostępna na naszym komputerze – zależy to prawdopodobnie od modelu jego płyty głównej. W moim przypadku powyższy kod działał bez problemu na komputerze stacjonarnym, lecz nie na laptopie. Może to i dobrze… Mam bowiem uzasadnione przeczucie, że temperatura tamtejszego procesora wcale by mi się nie spodobała ;)
Panuje obecnie moda na to, aby strony WWW jak najskuteczniejszej udawały, że nimi nie są i zamiast tego mieniły się ‘aplikacjami internetowymi’. Duża w tym zasługa możliwości JavaScriptu w nowoczesnych przeglądarkach. Jeśli potrafimy przeboleć pisanie w nim, to możemy osiągnąć całkiem efektowne rezultaty. Albo takie nieco mniej imponujące, ale też zgrabne :)
Do tej drugiej kategorii należy efekt znany jako watermark textbox, niemający wbrew pozorom żadnego związku ze znakami wodnymi w obrazkach. Trik ten – z którym zapewne tutejsza większość się zetknęła – polega na wyświetlaniu nieco przyszarzonego komunikatu w polu tekstowym, który następnie znika, gdy użytkownik umieści w nim kursor. Oprócz sprawiania wrażenia, że “strona żyje”, ta prosta sztuczka ma też wymiar praktyczny: pozwala oszczędzić na zbędnej etykiecie textboxa.
Chcąc “wodne” pole tekstowe umieścić na swojej stronie, możemy skorzystać z jednego z gotowych rozwiązań. Wiele z nich używa jQuery – darmowej biblioteki ułatwiającej pisanie w JS. Jeśli jednak nie korzystamy z niej w innych miejscach strony, to wymaganie jej obecności nie musi się nam podobać. A ponieważ watermark textbox jest efektem łatwym do zakodowania, spokojnie możemy napisać go własnoręcznie. Prześledźmy zatem, jak da się to zrobić.
Jednym z powodów, dla których nie lubię diagramów blokowych, jest ich prymitywna notacja przedstawiania instrukcji sterujących. Jedyne, czym się tam dysponuje, to if
i skok. Jak przy pomocy tylko tego można w klarowny sposób przedstawić jakikolwiek nietrywialny algorytm? To trochę tak, jakby w programowaniu używać tylko pętli nieskończonych i instrukcji break
.
Można to robić, oczywiście, ale konsekwencje nie są zbyt ciekawe. Jeśli kryterium opuszczenia pętli jest tak skomplikowane, że trzeba je rozbić na kilka konstrukcji if
–break
i żadna z nich “nie zasługuje” na wyciągnięcie do warunku w samym while
/for
, to coś tu brzydko pachnie. Nie chciałbym być w skórze tego, kto będzie musiał później takie dzieło debugować.
W bardziej rozsądnych przypadkach da się uniknąć pętli nieskończonych, nawet jeśli od razu nie widać, jak można by to zrobić. I tak odkryłem na przykład, jak można to ładnie zrobić w sytuacji podobnej do poniższej:
Nazywam to pętlą z wyróżnionym startem, bo typowa jej postać, która nie zawiera (pozornie) nieskończonego kręcenia się w kółko, wygląda tak:
Cała zabawa polega na tym, że pierwsze wywołanie SomeFunction
może dać niepoprawną wartość w foo
. (Może to być jakieś wyszukiwanie w pojemniku i element odpowiadający kluczowy sth
nie został w ogóle znaleziony). Sama treść pętli nie może być wtedy wykonana, bo zdarzy się zapewne jakieś nieszczęście. Dlatego trzeba wyodrębnić to pierwsze wywołanie, i oczywiście zadbać także o kolejne. A to daje, jak widać, dublowanie kodu. Chciałoby się to zrobić lepiej.
I można, na szczęście. Możemy za to podziękować feature‘owi C++, który często bywa krytykowany jako ułatwiający popełnianie błędów. Bowiem to dzięki temu, że instrukcja przypisania zwraca wartość przypisywaną, możemy to wszystko zapisać tak:
Zdaję sobie sprawę, że dla wielu fakt ten jest równie oczywisty jak pisanie return a == b;
zamiast:
ale dla niektórych taka postać pętli for
może być zupełnie zaskakująca. Pamiętajmy, że kiedyś (prawie) każdy pisał też i takie if
y jak powyżej ;-)
Tytuł dzisiejszej notki nie ma nic wspólnego z miejscowością wypoczynkową na południu Chorwacji, chociaż pewnie obecne temperatury nasuwają takie skojarzenia :) Zamiast tego chodzi o split łańcucha znaków, czyli bardzo często potrzebną w praktyce operację na stringach.
Danymi dla niej są najczęściej dwa napisy, zaś wynikiem jest tablica podciągów pierwszego z nich, powstała poprzez podzielenie go względem wystąpień drugiego (tzw. separatora). Ponieważ jak zwykle przykład będzie mówił najwięcej, niniejszym podaję nawet kilka:
Zwłaszcza ostatni pokazuje, że split jest rzeczywiście użyteczną funkcją, mogącą ułatwiać parsowanie formatów tekstowych (zwłaszcza, jeśli daje się ją zastosować wielokrotnie). Warto więc mieć takową w swoim języku/bibliotece. Jak więc przedstawia się jej dostępność na różnych platformach?
Tradycyjnie w .NET i Javie jest dobrze, a nawet lepiej. W obu przypadkach funkcja (a właściwie metoda) Split
/split
klasy String
dodaje nawet trochę więcej możliwości niż to opisałem wyżej. I tak w .NET można podać więcej niż jeden oddzielacz, natomiast w Javie domyślnie może być nim również wyrażenie regularne.
Niektóre języki skryptowe i skryptopodobne też mają się w tym względnie całkiem dobrze. W Pythonie jest metoda split
w klasie napisu, natomiast PHP ma funkcję explode
, która mimo innej nazwy działa bardzo podobnie.
Ale nie wszędzie funkcja typu split jest od razu dostępna; niekiedy trzeba ją sobie samemu napisać. Przykładem języka, gdzie może być to koniecznie, jest Lua oraz – jakżeby inaczej – C++ :) Ze względu na użyteczność splita często znajdowałem się w sytuacji, gdzie konieczne/wygodne było jego napisanie. Po kilku(nastu?) takich przypadkach doszedłem wreszcie do czegoś podobnego do poniższego kodu:
typedef std::vector
StringArray Split(const std::string& text, const std::string& delim)
{
StringArray res;
if (delim.empty()) { res.push_back(text); return res; }
std::string::size_type i = 0, j;
while (i < text.length()
&& (j = text.find(delim, i)) != std::string::npos)
{
res.push_back (text.substr(i, j - i));
i = j + delim.length();
}
res.push_back (text.substr(i));
return res;
}[/cpp]
Na koniec zwrócę jeszcze uwagę na to, że czasami trzeba ostrożnie postępować z rezultatem splitu. Zdecydowana większość wersji tej operacji dopuszcza, by w wynikowej tablicy występowały puste ciągi. Odpowiadają one kilku kolejnym wystąpieniom separatora lub jego obecnością na początku bądź końcu ciągu. Jeśli nie są one nam potrzebne (a rzadko są), to należy je zignorować lub usunąć.
Silnikologia (przynajmniej ta warsztatowa) ma swoje dziwnostki. Za jedną z nich uważam przykładanie zbyt dużego znaczenia do tych podsystemów engine‘u gry, które są zdecydowanie mniej ważne niż silnik graficzny czy fizyczny. Podpadają pod to (między innymi): mechanizmy logowania, VFS-y (wirtualne systemy plików), kod odpowiedzialny za wczytywanie i zapisywanie opcji konfiguracyjnych, itp. Blisko szczytu tej listy znajduje się też podsystem, o którym chcę napisać dzisiaj kilka słów. Chodzi mianowicie o menedżer zasobów (resource manager).
Pod tą nazwą kryję się obiekt, którego zadaniem jest – jak nazwa zresztą wskazuje – zarządzanie różnego rodzaju zasobami gry, gdzie pod pojęciem ‘zasobu’ kryje się zwykle jakiś element jej contentu: model, tekstura, próbka dźwiękowa, czcionka, shader… Menedżer ma za zadanie udostępniać odwołania do tych zasobów elementom silnika/gry, które ich potrzebują. To jest jego główny, nadrzędny cel.
Tu jednak dochodzimy do małego paradoksu. Jeśli bowiem jest to jedyne przeznaczenie modułu od zarządzania zasobami, to tak naprawdę mogłoby go w ogóle nie być!… W praktyce jego istnienie usprawiedliwia przynajmniej jeden z poniższych powodów:
Tego rodzaju wymagania rzeczywiście można rozsądnie zrealizować dopiero wówczas, gdy w logiczny sposób wydzielimy z kodu część, która za kontrolę zasobów będzie odpowiadać. Jeśli jednak żadna z powyższych sytuacji nie aplikuje się do nas, nie ma żadnego powodu, by zabierać się za tworzenie modułu zarządzania zasobami tylko dlatego, że “przecież zasoby gdzieś muszą być”. Coś takiego jak ‘prosty menedżer zasobów’ to oksymoron: jeśli faktycznie może być prosty, to najpewniej znaczy, iż jest zbędny. No, chyba że pod tym pojęciem rozumiemy też coś, co w praktyce da się zredukować do:
W takim przypadku powinniśmy zadbać przede wszystkich o to, by nasz Menedżer Zasobów (caps intended) dobrze współpracował z Modułem Wyświetlania Życia Gracza, że o Silniku Od PauzyTM nie wspomnę ;P
Gry i inne podobne aplikacje wymagają precyzyjnego pomiaru czasu, aby realizować obliczenia związane z fizyką, animacją, itp. Jak niemal wszystko, API potrzebne do tego celu jest różne w zależności od platformy, czyli (głównie) systemu operacyjnego. Dzisiaj chciałbym pokazać, jak wygląda to na dwóch najpopularniejszych systemach: Windows i POSIX (a więc między innymi na wszelkiego typu Linuksach).
Interfejs programistyczny systemu spod znaku okienek udostępnia dwie funkcje od dokładnego mierzenia czasu. Są to
QueryPerformanceFrequency
i QueryPerformanceCounter
. Tę pierwszą wywołuje się tylko raz, a jej wynikiem jest częstotliwość wewnętrznego systemowego zegara, który służy do precyzyjnego pomiaru upływającego czasu. Wyrażana jest ona w liczbie “tyknięć” na sekundę i na dzisiejszym sprzęcie może być bardzo duża, bo liczona w (kilku(nastu/dziesięciu)) milionach.
Druga funkcja jest używana nieporównywalnie częściej, gdyż to ona zwraca aktualną wartość zegara, czyli liczbę jego “tyknięć”. Stąd wynika, że obliczenie tej wartości w sekundach wymaga podzielenia rezultatu QPC
przez uzyskaną wcześniej częstotliwość:
Używana tu unia LARGE_INTERGER
to nic innego, jak zwykła liczba 64-bitowa.
W systemach POSIX-owych rzecz jest odrobinę prostsza, jako że tutaj dokładność zegara jest ściśle ustalona. Funkcja gettimeofday
(z nagłówka sys/time.h) zwraca rezultat z precyzją mikrosekundową w postaci struktury timeval
:
Część odpowiadającą niepełnym sekundom (pole tv_usec
) trzeba więc podzielić przez milion.
Chcąc mieć bardziej uniwersalne rozwiązanie, możemy napisać klasę opakowującą zegar z implementacją kontrolowaną docelową platformą, na którą kompilujemy:
Można by na koniec zapytać jeszcze, co tak naprawdę znaczy zwracana wartość zegara. Kiedy wynosi ona zero?… Otóż wbrew pozorom odpowiedź na to pytanie nie jest istotna, bo w praktyce interesuje nas tylko interwał czasowy, tj. różnica między dwoma wskazaniami timera. To na jej podstawie liczymy zmianę w prędkościach obiektów, klatkach animacji czy w końcu niezbędnie konieczną do wyświetlenia wartość FPS :)