Bardzo popularnym, często powielanym błędem podczas deploymentu na Windowsa jest czekanie na błędy i wrzucanie bibliotek „na pałę” do folderu z binarką aplikacji, jest to pracochłonne, i działa tylko w niektórych przypadkach:
Trochę lepszym sposobem jest używanie dependency walker który, skanuje plik exe w poszukiwaniu zależności, i wyświetla brakujące biblioteki.
Zacznę od aplikacji
nieużywających komponentów Qt Quick
, do tego celu użyjemy narzędzia
windeployqt
, znajduje się ono w katalogu Qt, podkatalogu wersji, podkatalogu
mingw
, i wreszcie w folderze
bin
w moim przypadku jest to
C:/Qt/5.9/mingw53_32/bin
.
Najczęściej popełnianym błędem, który i ja popełniłem jest użycie złej wersji narzędzia
windeployqt
, dlatego wcześniej warto się upewnić jakiej wersji Qt użyliśmy do budowy aplikacji.
Teraz czas uruchomić
cmd
i wywołać
windeployqt
wraz z odpowiednimi argumentami, w przypadku prostej aplikacji będzie to
Narzędzie automatycznie wykryje potrzebne biblioteki, znajdzie je i dorzuci do katalogu z naszym plikiem exe.
W przypadku aplikacji Qt Quick, należy dodatkowo załączyć adres projektu, wynika to z tego, że aplikacja wymaga dynamicznie zaczepionych plików qml od komponentów które zostały użyte, w tym celu używamy windeployqt zgodnie ze składnią:
Tu warto wspomnieć, że częstym
błędem
jest mylenie folderu projektu, z folderem
qml
który znajduje się obok folderu
bin
.
Przykładowe użycie:
Po więcej argumentów w razie potrzeby należy wywołać:
Polecam też zajrzeć na http://doc.qt.io/qt-5/windows-deployment.html .
]]>
Niektóre z tytułów wręcz są popularne ze swojej prymitywnej S.I.:
Policjanci w GTA.
Wbrew popularnej opinii sztuczna inteligencja policjantów w San Andreas nie jest aż taka prosta, jednak można by było ją trochę doszlifować. W tym artykule rozpoczniemy tworzenie podstawowego zmysłu dla przeciwników – zmysłu wzroku i wprowadzimy sobie prosty interfejs do debuggingu.
Zaczniemy od kodu finalnego z mojego poprzedniego artykułu: Jak programować gry? Najważniejsze elementy w grach 2D. Kod źródłowy z tamtego artykułu znajdziecie tutaj: GitHub
Rozpoczniemy od stworzenia klasy gracza oraz klasy przeciwnika, które nazwiemy odpowiednio
CPlayer
oraz
CEnemy
. Następnie stworzymy prosty kontroler dla obydwu, a dodatkowo w kontrolerze gracza dodamy możliwość poruszania się.
Podobny schemat realizowałem już w filmie o tworzeniu bombermana, jednak przewiduję, że prawdopodobnie go nie obejrzałeś (bo komu chciałoby się oglądać w całości film o tworzeniu gry trwający 2 godziny). Z tego względu jeszcze raz wytłumaczę, co zrobiliśmy:
I) Utworzyliśmy klasę gracza i jego kontrolera:
CPlayer
– klasa gracza
CPlayerController
– klasa kontrolera gracza zawierająca sterowanie
Opisałem kod tak, by było dokładnie widać co krok po kroku trzeba wykonać.
Plik nagłówkowy
Player.hpp
:
Plik źródłowy
Player.cpp
:
II) Utworzyliśmy klasę wroga i jego kontrolera. Póki co nasze A.I. zostawimy w spokoju. Zajmiemy się nim w następnej części tego artykułu.
CEnemy
– klasa wroga
CEnemyAIController
– klasa kontrolera S.I. wroga
Plik nagłówkowy
Enemy.hpp
:
Plik źródłowy
Enemy.cpp
:
III) Wczytaliśmy potrzebne tekstury przy uruchamianiu gry:
IV) Stworzyliśmy gracza i jednego wroga, a następnie dodaliśmy ich do poziomu:
Sztuczna inteligencja w grach jest oparta o różne zmysły np.: zmysł wzroku, zmysł słuchu czy nawet czasem zmysł węchu. Najprostszym do zaimplementowania jest zmysł wzroku, który jak zapewne łatwo jest się domyślić, będzie odpowiadał za widzenie. Będzie on działał w ten sposób, że będzie sprawdzał, czy jest coś interesującego w zasięgu wzroku oraz w obszarze widoku (kąt widzenia zrobimy łatwy do dostosowania) i jeśli coś wykryje, to zwróci to w wyniku.
Na początku, jako że wszystkie zmysły będą miały kilka wspólnych metod i własności, utworzymy sobie klasę bazową. Nazwałem ją IAISense (w swoich projektach przedrostek „I” dodaje do klas bazowych, dostarczających swego rodzaju interfejs do późniejszego rozbudowywania, jednak takich, które nie mogą być bezpośrednio używane). Do implementacji stworzymy sobie osobne pliki –
AISense.hpp
i
AISense.cpp
.
Teraz, zanim przejdziemy do kodu, zastanówmy się, co będzie musiał mieć każdy zmysł. Po chwili rozmyślań doszedłem do wniosku, że:
Będąc świadomym tego, do czego dążymy, utworzyłem następującą klasę bazową:
Warto bardzo dokładnie przeanalizować komentarze, szczególnie ten przy metodzie QueryActors, bo jest to kluczowa metoda tej klasy. Teraz do zaimplementowania został nam tak naprawdę tylko konstruktor (zwróć uwagę, że
QueryActors
jest metodą czysto wirtualną,
GetSensedActors
jest zdefiniowana jako
inline
).
Kod konstruktora jest bardzo prosty:
Pokusiłem się tutaj o referencje na klasę
IPawn
(lub bazową) właściciela. Takie rozwiązanie wymusza podanie go (gdybyśmy użyli wskaźników, ktoś mógłby podać np. pusty wskaźnik), przez co reszta kodu, która się do niego odnosi, zadziała poprawnie.
Następnym etapem będzie stworzenie zmysłu wzroku. Aby mieć jakiś wgląd w sposób jego działania, spójrz na poniższy obraz:
Zatem mamy do stworzenia kolejną klasę, która będzie zawierała kąt i zasięg widzenia oraz implementacje metody
QueryActors
, która sprawdzi, czy aktorzy są polu widzenia. Zdecydowałem, że umieszczę tę klasę również w plikach
AISense.hpp
i
AISense.cpp
by nasz projekt nie miał za chwile 40 plików.
Tak oto wygląda ta klasa:
Tym razem do zaimplementowania mamy konstruktor oraz trzy metody. Może zajmijmy się najpierw konstruktorem:
Nie wrzuciłem ustawienia
m_sightDistance
i
m_sightAngle
do listy inicjalizacyjnej, ze względu na to, że mamy od tego odpowiednie funkcje, które zapobiegają wprowadzeniu nieodpowiednich danych.
Teraz spójrzmy dalej do metod
SetSightDistance
i
SetSightAngle
. Odpowiednie środki bezpieczeństwa są tutaj wymagane. Nie chcemy przecież mieć ujemnego zasięgu lub kąta widzenia > 180 stopni. Warto również zauważyć, że podane funkcje zwracają
true
,
jeśli poprawnie ustawiono wartość a
false
,
jeśli wartość była niepoprawna. Nie będziemy póki co z tego korzystać, ale warto mieć coś takiego zaimplementowanego – może niedługo się przyda
Przyszedł czas na kluczowy moment. Teraz zajmiemy się całą logiką zmysłu. Pomyślmy, co powinien on krok po kroku zrobić:
Problemem jednak jest to, że w poprzednim artykule nie stworzyliśmy sobie metody, dzięki której zyskamy dostęp do listy aktorów ze sceny. Dlatego właśnie potrzebna jest nam poniższa funkcja, którą dodałem do klasy
CLevel
:
Na tych założeniach zbudowałem taką funkcję. Polecam przeanalizować po kolei każdy etap jej działania, gdyż właśnie jesteśmy w punkcie kulminacyjnym tego artykułu.
Uff… już najtrudniejsze za nami. Teraz możemy dodać już zmysł wzroku do wroga.
CSightSense m_sightSense; // Zmysl wzroku wroga
Należy również pamiętać o prawidłowym wywołaniu konstruktora zmysłu z poziomu listy inicjalizacyjnej. Następnie, aby nasz zmysł „działał” musimy go ciągle uaktualniać. Do tego posłuży nam metoda
IActor::Update
, którą sobie przeładujemy. Musimy jednak pamiętać, że metoda ta również ma swoją implementację w klasie bazowej
IPawn
, więc musimy umieścić też odwołanie do implementacji bazowej:
Co nam jednak z tego, że tak się narobiliśmy, a wciąż nie widzimy efektów? Właśnie dlatego teraz zaimplementujemy…
Wyszukiwanie błędów w grach jest bardzo uciążliwe, jeśli przed sobą mamy same cyferki i nic konkretnego. Dlatego właśnie wiele gier dodaje sobie prosty panel debugowania „in-game”. Tym właśnie się teraz zajmiemy.
Na samym początku już wiemy, że potrzebna będzie nam funkcja generująca kształt wycinka koła. Jako że SFML sam jej nie dostarcza, postanowiłem napisać ją sam. Stworzyłem więc plik z deklaracją
SFMLShapes.hpp
oraz plik źródłowy
SFMLShapes.cpp
. Możliwe, że w przyszłości potrzebne będzie nam więcej własnych kształtów i wtedy umieścimy ich generowanie również w tych plikach.
Nazwałem ten kształt Pie (odnosi się do angielskiej nazwy ciasta, ponieważ nasz kształt to jakby wycinek ciasta ).
Powyższa funkcja nie jest w pełni funkcjonalna, jednak wystarcza do podstawowych zastosowań. Jej mankamentem jest to, że używa
sf::ConvexShape
(convex – wypukły). Może to spowodować niechciany efekt przy wyświetlaniu wycinka o kącie > 90 stopni. Jeśli ktoś ma na tyle ochoty, żeby się z tym bawić, to polecam do tego użyć
sf::VertexArray
.
Teraz już możemy wykorzystać ten generator. Pamiętamy jeszcze klasę
IAISense
? Każdy zmysł będzie mógł się popisać jakimś fajnym symbolem przy debuggingu, dlatego też utworzymy metodę dla tej klasy, która będzie rysowała takie symbole na ekranie.
Metoda ta, nie jest metodą czysto wirtualną, bo zmysł ma mieć możliwość wyświetlenia debug info, ale nie jest do tego zmuszany.
Teraz czym do diabła jest to
static bool DebugMode;
?
Dobrym pomysłem jest posiadanie jakiegoś przełącznika, którym będziemy sterowali, by albo włączyć tryb testowy, albo go wyłączyć. Statyczna zmienna
DebugMode
jest właśnie takim przełącznikiem. Oczywiście pamiętamy o tym, że taką zmienną statyczną trzeba też zainicjalizować. Najprościej będzie zrobić to na początku pliku źródłowego:
bool IAISense::DebugMode = true;
Mamy już bazę, to teraz trzeba zaimplementować wyświetlanie informacji trybu testowego dla zmysłu wzroku. Zrobiłem to tak:
Weźmy głęboki oddech… w tym miejscu artykuł ten możemy zakończyć jedną linijką, którą dodamy, by nasz debug info mógł się w ogóle wyświetlić. Jak zapewne się już domyśliłeś, umieścimy ją w metodzie wyświetlającej wroga na ekranie.
m_sightSense.DrawDebug();
W tym artykule zbudowaliśmy sobie podstawę pod dalsze rozwijanie modułu sztucznej inteligencji. Dodaliśmy również możliwość wyświetlania podstawowych informacji debuggingu. Kod z tego artykułu znajdziesz tutaj: GitHub . Teksturki można pobrać tutaj: Mega .
]]>
std::array
.
Zakładam, że wiesz co to tablica, prawda? Ot, zbiór elementów jednego typu poukładanych w pamięci jeden za drugim. Nic strasznego, nic trudnego. Jest to bardzo prosta rzecz, można by rzec nawet, że aż za prosta – standardowy C-style array nie posiada w sobie nic ciekawego, żadnych iteratorów, żadnych fajnych metod dostępu do niego, wszystko trzeba robić „ręcznie”.
I tutaj na scenie pojawia się
std::array
z biblioteki
array
, dodany w standardzie
C++11
Ważne – (zazwyczaj) domyślnie każdy kompilator korzysta ze standardu C++03. Żeby skorzystać z nowszych, należy to sprecyzować przy użyciu flagi -std=c++11/14/17 (zależnie od standardu), albo w opcjach kompilatora – ale to zostawiam już do samodzielnego ogarnięcia).
Jest to bardzo przyjemna alternatywa, posiadająca dużą wartość użytkową – po prostu ułatwia programiście życie. No dobra, ale co w tym takiego fajnego? Dlaczego miałbym się przestawić nagle na
std::array
, zamiast używać starego dobrego c-style arraya?
Używanie
std::array
jest prawie takie samo jak używanie zwykłego c-style arraya – losowy dostęp do elementów umożliwia standardowy operator
[]
, ale oprócz tego ten kontener posiada kilka bajerów:
Po pierwsze –
std::array
posiada tak zwany
iterator
. Iterator to obiekt wskazujący na jakiś element z zakresu elementów, który potrafi iterować (czyli, potocznie mówiąc,
„przeskakiwać”
) między elementami przy pomocy na przykład operatorów inkrementacji czy dekrementacji (ale częściej iteratorów używa się ich w range-based for’ach na przykład, o czym za chwilę). Jest to duże ułatwienie w sytuacjach gdzie musimy na przykład przelecieć całą tablicę od początku do końca (lub też od końca do początku, bo
std::array
posiada także reverse iterator!)
Po drugie – posiada przeładowane operatory porównania
==
,
!=
,
<
,
<=
,
>
,
>=
. Czyli możemy w łatwy sposób porównywać dwa std::array’e. Dodatkowo, ma przeładowania dla
std::swap
i
std::get
, gdybyśmy potrzebowali w fikuśny sposób dostać się do danego elementu tablicy lub zamienić jej zawartość z zawartością innej.
Po trzecie – posiada kilka użytkowych metod, takich jak
fill
(wypełnienie tablicy pewną wartością),
front
/
back
(dostęp do pierwszego/ostatniego elementu),
empty
(sprawdzenie czy tablica jest pusta),
size
/
max_size
(sprawdzenie aktualnej i maksymalnej ilości elementów). Jest to też typ agregowalny, czyli przykładowo można go inicjalizować poprzez klamry
{}
, tak jak zwykłą tablicę.
Podsumowując –
std::array
jest prawie taki sam w użytkowaniu jak zwykły c-style array, tylko fajniejszy oraz często wygodniejszy.
Jak już wcześniej mówiłem, można używać
std::array
tak samo jak zwykłej tablicy. Inaczej wygląda deklaracja, ponieważ jest to zwyczajna struktura z szablonowymi typami w konstruktorze:
template<class T, std::size_t N> struct array;
Gdzie T to typ danych przechowywanych w naszym kontenerze, a N to jego początkowa wielkość.
std::size_t
to ogólnie mówiąc typ danych dla liczb całkowitych bez znaku.
Warto o tym pamiętać, polecam przy odnoszeniu się do wielkości kontenerów używać
std::size_t
zamiast int’a czy jakiegokolwiek innego typu danych, oszczędzi nam to ostrzeżeń kompilatora mówiących o porównywaniu typów ze znakiem i bez niego.
Przykładowe deklaracje takich kontenerów wyglądają następująco
No i fajnie, mamy jakieśtam tablice. Co teraz z nimi zrobić? Wykorzystajmy dla przykładu iteratory.
Ten kod chyba wymaga tłumaczenia, szczególnie jeśli ktoś wcześniej nie miał kontaktu z nowszymi standardami C++.
Więc tak – pierwszy przykład wyświetla nam zawartość
std::array
przy użyciu range-based for’a.
const auto &v
to stała referencja do kolejnych wartości kontenera, iterator automatycznie inkrementuje się co iterację pętli. Dlaczego odnoszę się do elementów w ten sposób? Mógłbym to zrobić klasycznie, poprzez
int v
, ale w ten sposób każdy z elementów byłby kopiowany do tymczasowej zmiennej
v
, co w przypadku większych kontenerów stwarzało by duży problem wydajnościowy. Odniesienie się poprzez referencję pozwala na kopiowanie jedynie adresu do elementu, unikając kopiowania jego samego, a
const
uniemożliwia zmianę danego elementu (bo nie chcemy go przecież zmieniać). W tym wypadku jest to na tyle mała skala że w praktyce nie zauważymy żadnych wzrostów wydajności, ale warto wyrobić sobie praktykę używania referencji. Jeśli chodzi o
auto
, to jest to tylko kwestia wygody – ten keyword i tak zostanie zamieniony przez kompilator na odpowiedni typ danych.
W następnym kawałku kodu wypełniamy sobie tablicę bezpośrednio odnosząc się do iteratorów –
b.begin()
wskazuje na początek,
b.end()
na koniec naszego kontenera (analogicznie,
b.rbegin()
wskazuje na koniec, a
b.rend()
na początek). Jak wcześniej mówiłem, iteratory mają przeładowane operatory – inkrementacja, dekrementacja i tego typu rzeczy, które są bardzo wygodne.
Dalej, modyfikacja elementów przy użyciu range-based for’a, tutaj używam referencji bez
const
ponieważ modyfikuję elementy (i w tym wypadku nie mogę odnieść się bez referencji, ponieważ bez niej operował bym na kopiach elementów tablicy)
I na koniec smaczek, przelatuję sobie tablicę od końca co drugi element.
Oczywiście to tylko kilka przykładowych porównań, ale widzimy, że
std::array
jest o wiele bardziej czytelniejszy niż standardowa tablica.
Przyszedł czas na krótkie podsumowanie naszego kontenera
std::vector
I w sumie tyle. Jeśli masz jakieś wątpliwości, nadal czegoś nie rozumiesz, zapomniałem o czymś, masz jakąś sugestię czy cokolwiek – pisz śmiało w komentarzu.
A po pełną referencję i więcej przykładów dla tego kontenera zapraszam tutaj: http://en.cppreference.com/w/cpp/container/array .
Następny rozdział będzie poświęcony kontenerowi
std::vector.
Wolny program? Hurr durr, zrób wątki.
Brzmi znajomo? Jeśli program działa wolniej niż się chce inni programiści dają z reguły dwie rady: pierwsza, bardzo dobra – poprawić algorytm, druga, zwykle fatalna – użyć wielu wątków / procesów. Prawda jest taka, że czego by nam nie pchali producenci 12–rdzeniowych procesorów w telefonach wiele wątków ma tyle zalet co wad. Sama synchronizacja pochłania tyle mocy obliczeniowej, że o jednym rdzeniu można zapomnieć (drobna przesada nikogo nie zabiła :P), pełno miejsca na świeżutkie wycieki pamięci czy niezdefiniowane wyścigi po dane, jednym słowem – chaos.
Użyć jednego rdzenia, ale lepiej. Jak? Tu właśnie do akcji wkracza SIMD ( S ingle I nstruction, M ultiple D ata – z ang. jedna instrukcja, wiele danych). Sztuczka polega na klasycznym przeprowadzeniu jednej operacji jak dodawanie, odejmowanie czy dzielenie, ale zamiast jednego lub kilku operandów używa się całych zestawów danych, dobrze ilustruje to rysunek poniżej. Takie podejście do problemu znane jest od dawna, ale ręcznie wykorzystywane w praktyce dosyć rzadko, na rynku jest tak dużo procesorów, że programista podchodzący poważnie do tego co robi często nie wie czy jego program zostanie w ogóle uruchomiony na x86(_64), jak tu liczyć na konkretne rozszerzenie jak np. SSE4.2 , AVX2 albo NEON ? Dlatego w przypadku C++ (o nim głównie będzie ten wpis) liczyło się z reguły na kompilator, że zoptymalizuje to tu, to tam. Ale kompilator nie jest wszechwiedzący, dlatego dodatkowe 16 256–bitowych rejestrów w naszym nowiutkim procesorze może zostać niewykorzystane. A co jak co, 256–bitowe rejestry to nie lada zabawka.
Teraz przyszły jednak piękne czasy, kiedy na pytanie „SIMD?” odpowiadamy „ Boost.SIMD ”, tj. nazwą dość świeżej biblioteki z rodziny boost (swoją drogą polecam jeśli ktoś nie kojarzy), która za nas zajmie się doborem technologii, tyle że w przeciwieństwie do optymalizacji kompilatora możemy dostosować kod do takiego równoległego wykonania. Przykładowo iloczyn skalarny na procesorze wspierającym AVX (nie AVX2, to jest spotykane na zbyt niewielu procesorach żeby o tym pisać) można obliczyć ponad trzykrotnie szybciej – jednym rdzeniem.
Już wiemy co i jak z tym Boost.SIMD i SIMD w ogóle, więc teraz trzeba sprawdzić jak to działa.
Uwaga
– Boost.SIMD wymaga do pracy biblioteki boost w wersji 1.60 lub nowszej, inaczej pojawi się błąd związany z
boost::enable_if
(a może jeszcze jakiś inny).
Biblioteka jest na tyle dobrze zaprojektowana, że do nauczenia się jej obsługi w stopniu pozwalającym na przyśpieszenie wielu algorytmów wystarczy omówić prosty (ale nie za prosty) przykład, wybrałem wcześniej wspomniany produkt skalarny – idealnie pasuje do sytuacji (wydaje mi się, że ten przykład wykorzystano też w jakimś anglojęzycznym kursie, ale nie mogłem go znaleźć).
Na początek warto rozwiązać ten problem bez klawiatury, produkt skalarny można opisać tak: P = a
1
×b
1
+ a
2
×b
2
+ … + a
n
×b
n
.
To teraz naiwna implementacja liczenia produktu dla dwóch instancji
std::vector
(z dowolnym alokatorem).
Poza komentarzami kod chyba nie wymaga dalszych wyjaśnień. Teraz pora na ciekawszą część z wykorzystaniem Boost.SIMD. Na początek kilka linii przygotowujących nas do właściwego wykorzystania biblioteki.
Warto pamiętać, że jeśli zamiast
float
wykorzystacie
double
ilość operacji prowadzonych na raz (
cardinal
) najprawdopodobniej zmaleje dwukrotnie. A wracając do produktu skalarnego, tutaj algorytm jest dalej prosty, ale już mniej oczywisty, najwygodniej opisać go zwykłym zdaniem:
Dzielimy vectory na pary paczek o rozmiarze
cardinal
, równolegle mnożymy paczki otrzymując nową o tym samym rozmiarze i doliczamy sumę jej elementów do produktu, jeżeli rozmiar vectorów nie jest wielokrotnościącardinal
resztę doliczamy liniowo.
Teraz może wydawać się to troszkę zawiłe, ale kilka linii kodu rozwiewa wątpliwości:
Prosty teścik wydajności obu rozwiązań:
W moim przypadku (kompilowane z flagą
-mavx
) liniowe rozwiązanie wykonywało się średnio 4522ms a to używające Boost.SIMD – 1305. Prosta matma mówi że to drugie jest ~3.47 raza szybsze, fajnie, nie? Ps. Nie tłumaczę kodu do testu, bo to głównie standard C++, jedyne o czym warto wspomnieć to alokator
bs::allocator<float>
– upewnia się że dane w wektorze są odpowiednio wyrównane co bywa niezbędne do sprawnego wykorzystania instrukcji SIMD, jednak nie wpływa w żaden (odczuwalny) sposób na czas wykonania liniowej implementacji.
I to by było na tyle. Wydaje się za proste? Mi też, ale ta wiedza wystarczy do wydajnego policzenia niejednego zadania, jeśli będziecie chcieli wiedzieć coś więcej to dokumentacja Boost.SIMD zaprasza. :). Bo nie łudźmy się, kod ode mnie jest szybki, ale nie najszybszy – trochę pracy i na procesorze z AVX powinniście móc osiągnąć co najmniej 6-krotny wzrost wydajności. Cały kod dla zainteresowanych .
]]>
Dobra, jak się za to zabrać? Zróbmy niebieski ekran, w tym celu do
ApplicationWindow
podpinamy właściwość, jaką jest kolor i ustawiamy na „blue”, teraz trzeba zrobić, aby nasza aplikacja nie wyglądała jak aplikacja, w tym celu dodajemy właściwość
flags
i wartość
Qt.FramelessWindowHint
, kod powinien wyglądać tak:
Teraz wypadałoby zmusić aplikację, aby była na pełnym ekranie. Jest parę sposobów, moim zdaniem najprościej będzie zaimportować moduł od okien
import QtQuick.Window 2.0
, następnie ustalić
width
okna na
Screen.width
i analogicznie
height
.
Teraz potrzebny nam jakiś tekst, w tym celu do
ApplicationWindow
„podłączamy” komponent Text, ustalamy kolor, wielkość, font i inne ficzery.
Całość powinna wyglądać tak:
Nasz program właściwie jest już gotowy, można jeszcze dostosować font, wielkość itp. Co do tzw. deploymentu , czyli doprowadzania aplikacji do stanu, w którym można ją uruchomić na innym komputerze – opiszę ten proces w kolejnym poradniku, jest to głębszy temat.
]]>Przytoczę pewne bardzo obrazowe porównanie. Wyobraź sobie, że jesteś w restauracji. Podnosisz menu, wybierasz potrawę i czekasz na kelnera. Widzisz, że tymczasowo obsługuje innego klienta. Mija kilka, kilkanaście minut, a kelner wciąż stoi przy stoliku innego klienta, obsługuje tylko jego. Dopiero, gdy klient płaci i wychodzi z restauracji, kelner podchodzi do Ciebie i zaczyna cię obsługiwać. W tym czasie przychodzi inny klient, ale kelner jest zajęty tylko tobą. Mniej więcej tak działa aplikacja jednowątkowa (nasz kelner). W oczywisty sposób jest to nieefektywne.
Wtedy niczym superbohater, przybywa wielowątkowość.
Super! A więc zyskujemy trochę wydajności. Jednak rozważmy ten kod w języku C# i przeanalizujmy go.
Co tu się dzieje? Program sprawdza czy plik examplefile.txt istnieje, jeżeli nie, tworzy go. Teraz przejdźmy do tego, dlaczego kod ten nie jest bezpieczny. Jako, że dziś mamy do czynienia z wieloma procesorami (rdzeniami), to w tle mogą działać równocześnie różne programy. Nie ma więc żadnej gwarancji, że jeden z nich nie utworzy pliku o nazwie examplefile.txt właśnie w tym katalogu. Rozchodzi się więc o moment pomiędzy otrzymaniem informacji, że plik nie istnieje, a utworzeniem go. Klasa tego błędu to tzw. Race Condition . Aby uniknąć takich sytuacji, wymagana jest synchronizacja.
Najprostszym używanym niskopoziomowym mechanizmem do synchronizacji są tzw. Spinlocki (w luźnym tłumaczeniu – kręcące się blokady). Jest on w zasadzie aktywną pętlą, która czeka, aż dany obiekt (blokada) zostanie zwolniony. Dziś w zasadzie się z nich nie korzysta, gdyż nowoczesne języki takie jak C# mają wbudowane mechanizmy synchronizujące, takie jak
lock
. Lock działa w ten sposób:
i jest równoważny temu:
gdyż
lock
jest w zasadzie aliasem tego drugiego. Teraz, zanim przejdę do spinlocków, wytłumaczę z pomocą Wikipedii czym jest owa sekcja krytyczna.
Sekcja krytyczna – fragment kodu programu, w którym korzysta się z zasobu dzielonego, a co za tym idzie w danej chwili może być wykorzystywany przez co najwyżej jeden wątek.
Myślę, że ta definicja spokojnie wystarczy. Przejdźmy teraz do Spinlocków.
W C++ można podejść do tego na prawdę prosto, jest to zwyczajnie (jak już pisałem) aktywna pętla. Wygląda to mniej więcej tak (korzystając z biblioteki <atomic> służącej właśnie do synchronizacji, i jak sama nazwa mówi do operacji atomowych):
Dzięki Spinlockom możemy także zsynchronizować uruchomienie wątków, aby wystartowały w mniej więcej tym samym czasie, co można zaimplementować bardzo prosto:
Wówczas nasza blokada może blokować(?) wykonanie wątku tak długo, jak programista sobie zażyczy. Słówka
volatile
użyłem, gdyż nie chce, by kompilator potraktował tę pętle tak:
if (slock != THREAD_COUNT) while(1);
a tego oczywiście nie chcemy.
C# również udostępnia nam mechanizm spinlocków, ba, w zasadzie całą strukturę! Napisałem tu krótki program implementujący Spinlock:
Przeanalizujmy, co się w nim dzieje.
static void Main(string[] args)
. Tworzymy w nim instancję struktury SpinLock, w bardzo standardowy sposób, oraz prywatne pole
int
o nazwie
_count
, którego zadaniem będzie przechowywanie pewnej wartości. Wewnątrz funkcji głównej tworzę prostą pętlę, w niej tworze natomiast nowy obiekt
Task
, który będzie asynchronicznie wykonywał naszą funkcję
vInc()
. Tam widzimy standardowy blok
try finally
, w którym to rozpoczynamy spin SpinLocka. Zauważ, że zanim wszedłem w aktywną pętlę, flagę
lockTaken
ustawiłem na
false
i jest przekazywana przez referencję, nie wartość.
To by było na tyle, jeżeli czegoś wystarczająco nie rozwinąłem, albo macie jakieś pytania, zachęcam do komentowania!
]]>
Qt – zestaw przenośnych bibliotek i narzędzi programistycznych dedykowanych dla języków C++ , QML i Java . Ich podstawowym składnikiem są klasy służące do budowy graficznego interfejsu programów komputerowych , począwszy od wersji 4.0 Qt zawiera też narzędzia do tworzenia programów konsolowych i serwerów. – Wikipedia
Może zacznę od motywacji, pisząc w C++ największym problemem jest multipletowość, niby jest, ale w praktyce trzeba się natrudzić żeby przepisać średnio zaawansowaną aplikacje z np. windowsa na linuksa, z pomocą przychodzi Qt, gdzie mamy gotową obsługę sieci, baz danych, zapytań, obsługi wielu wątków, obsługę jsona, xmla i wielu innych bardzo przydatnych pierdół. Przekonany?
Ale halo? Qt jest płatne , na co ty mnie naciągasz? Owszem, przy próbie pobrania na pierwszy rzut widać tylko możliwość kupienia/subskrypcji na zasadach trial, ale niżej jest opcja pobrania wersji otwartej, Community która prawie się nie różni od wersji płatnej.
Jeżeli znasz już podstawy C++ to super, jeżeli nie to odsyłam do książek lub Zelenta, ale i bez tego na start sobie poradzisz, no dobra, chcesz stworzyć projekt, ale tyle opcji do wybrania, co wybrać? I tu pojawia się kolejny problem z jakim początkujący walczą.
Najprostszym rozwiązaniem wydaje się Qt Widgets, ale czy aby na pewno? W Qt Widgets wszystko piszemy C++, ktoś może pomyśleć że super, znam C++ będzie łatwo, projektowanie interfejsu aplikacji używając samego C++ to jakieś nieporozumienie, cytując klasyka, co prawda jest opcja tworzenia interfejsu używając Designera, czyli specjalnego narzędzia dołączonego do Qt Creatora, jednak przy średnio zaawansowanych projektach designer często się crashuje i są duże problemy, lepiej się od niego nie uzależniać.
Co więc wybrać na początek?
Zdecydowanie polecam stworzenie projektu używając Qt Quick Controls, obecna najnowsza wersja to Qt Quick Controls 2, interfejs można malować używając banalnego języka frontendowego
QML
który składnią bardzo przypomina CSS.
Ujrzymy chaos, nasz projekt składa się z:
Zróbmy czerwony kwadrat na środku ekranu a w nim napis
Witam
.
W tym celu skracamy nasz kod do:
visible
– proporcja która definiuje to czy dany komponent jest widoczny
width
/
height
– szerokość / wysokość komponentu
title
– tytuł
Teraz dodajmy prostokąt, jak nazywa się prostokąt po angielsku? –
rectangle
,
nazwy komponentów zawsze piszemy z dużej litery, a zmiennych z małej.
Więc zróbmy prostokąt
Dobra, właściwości
width
i
height
chyba nie muszę ponownie tłumaczyć. color, definiuje kolor, można go zapisać na wiele sposobów,
czytaj więcej
.
Pojawiła się nowa zmienna –
anchors.centerIn
, zmienna ta definiuje to, że obiekt ma być w środku jakiegoś innego obiektu,
parent
to id obiektu na którego środku ma się znajdować nasz prostokąt, parent oznacza obiekt nadrzędny, ten który w hierarchii jest wyżej, warto zapamiętać. Równie dobrze w tym miejscu można wpisać jakieś id, każdy komponent może przyjmować wartość id, zaprezentuje to w kolejnym przykładzie niżej.
No okej, mamy okno, mamy jakiś brzydki, czerwony prostokąt, czas na tekst, jak to zrobić?
Analogicznie, do komponentu
Rectangle
należy „podczepić” komponent
Text
, robimy to w taki sam sposób jak
Rectangle
do
ApplicationWindow’a
, wypadałoby go wycentrować, użyj do tego
anchors.centerIn
, tekst ustalamy zmienna text, przykład
text: Witam
To twoje zadanie domowe
Możesz też poeksperymentować i zamiast tekstu dać
Button
, w tym celu zajrzyj do dokumentacji Qt Quick.