diff --git a/sprawozdanie/sprawozdanie.tex b/sprawozdanie/sprawozdanie.tex index bf3ef81..356bde7 100644 --- a/sprawozdanie/sprawozdanie.tex +++ b/sprawozdanie/sprawozdanie.tex @@ -95,7 +95,7 @@ tylko i wyłącznie swój czołg. \subsection{Przechowywany stan} Aktualny stan rozgrywki $S$ jest determinowany przez aktualne obiekty na mapie. Każda aktualizacja ze strony klienta, -wpływająca na stan $S$ powoduje zwiększenie licznika stanu o 1. Ze względu na trudność synchronizacji kolejnosci +wpływająca na stan $S$ powoduje zwiększenie licznika stanu o 1. Ze względu na trudność synchronizacji kolejności aktualizacji (np. stan 2 klienta A może być stanem 3 dla klienta B i odwrotnie) zdecydowano się, że każdy klient przechowuje ilość odebranych aktualizacji stanu od każdego klienta osobno. @@ -114,12 +114,12 @@ Z założenia, rozgrywka ma odbywać się w czasie rzeczywistym zatem wykorzysta wprowadzać niepotrzebne opóźnienia, wynikające z niepotrzebnych retransmisji pakietów. Komunikacja będzie odbywała się zatem za pomocą datagramów UDP, które pozwalają na szybszą - acz mniej dokładną -transmisję informacji pomiędzy klientami. W wypadku normlanej gry jednak strata części pakietów jest jak +transmisję informacji pomiędzy klientami. W wypadku normalnej gry jednak strata części pakietów jest jak najbardziej akceptowalna i nie wpłynie na rozgrywkę w znaczący sposób. W projekcie przyjęto architekturę klient-klient, tj. nie występuje żaden serwer stanowiący źródło prawdy dla rozgrywki. Zamiast tego jeden z klientów zawsze będzie miał status \textit{mastera}. Poprawnym stanem rozgrywki z definicji jest -stan symulacji \textit{mastera}. \textit{masterem} zawsze jest klient o najmniejszym \texttt{ClientId}, tj. klient, +stan symulacji \textit{mastera}. \textit{Masterem} zawsze jest klient o najmniejszym \texttt{ClientId}, tj. klient, który podłączył się najwcześniej. Takie podejście zapewnia rozwiązanie sytuacji, w której aktualny \textit{master} się rozłączy bez konieczności przeprowadzania negocjacji. @@ -142,52 +142,96 @@ rozłączy bez konieczności przeprowadzania negocjacji. \caption{Proponowana architektura sieciowa} \end{figure} -Dodatkowo, przyjmuje się zasadę, że każdy klient jest odpowiedzialny za swoją część symulacji, dla której -stanowi źródło prawdy. W praktyce oznacza to, że każdy gracz wysyła tylko i wyłącznie informacje o zmianach rzeczy, na które on ma wpływ - np. -pozycję własnego czołgu, czy wystrzelenie pocisku. W gestii innych klientów jest przetworzyć te informację i -zsynchronizować się. Przewidziano również obiekty o zachowaniu deterministycznym, które każdy klient może aktualizować +Dodatkowo, przyjmuje się zasadę, że każdy klient jest odpowiedzialny za swoją część symulacji, dla której stanowi źródło +prawdy. W praktyce oznacza to, że każdy gracz wysyła tylko i wyłącznie informacje o zmianach rzeczy, na które on ma +wpływ - np. pozycję własnego czołgu, czy wystrzelenie pocisku. W gestii innych klientów jest przetworzyć te informacje +i zsynchronizować się. Przewidziano również obiekty o zachowaniu deterministycznym, które każdy klient może aktualizować samodzielnie w ramach swojej symulacji. Takie podejście zapewnia brak konfliktu o zasoby, ponieważ każdy obiekt jest modyfikowany wyłącznie przez jedną osobę, zatem nigdy nie nastąpi konflikt. Nie występuje również problem odczytywania zmienionych danych ponieważ wyświetlanie oraz aktualizacja stanu gry odbywają się w ramach jednego wątku. -Ze względu na możliwość zgubienia niektórych pakietów, oraz chęć unikniecia skokowej aktualizacji rozgrywki po stronie -każdego klienta następuje predykcja zachowań innych graczy. W wypadku otrzymania pakietu aktualizujacego stan gracza, +Ze względu na możliwość zgubienia niektórych pakietów, oraz chęć uniknięcia skokowej aktualizacji rozgrywki po stronie +każdego klienta następuje predykcja zachowań innych graczy. W wypadku otrzymania pakietu aktualizującego stan gracza, predykcja jest odrzucana i wykorzystane są otrzymane dane. Ze względu na niedoskonałość mechanizmu predykcji, predykcje nie mogą modyfikować stanu gry w sposób nieodwracalny, tj -np. predykcja nie może usunąć obiektu z mapy - moze go natomiast oznaczyć jako prawdopodobnie usunięty, co za tym idzie +np. predykcja nie może usunąć obiektu z mapy - może go natomiast oznaczyć jako prawdopodobnie usunięty, co za tym idzie nie będzie on wyświetlany ani predykowany dalej (z punktu widzenia gracza zostanie usunięty) - w takiej sytuacji przywrócenie obiektu w wypadku błędnej predykcji wciąż będzie możliwe. -Każdy klient będzie obsługiwany przez 4 osobnych wątki, których kompetencje nie kolidują ze sobą: +Każdy klient będzie obsługiwany przez 4 osobne wątki, których kompetencje nie kolidują ze sobą: \begin{enumerate} \item Aktualizacja, predykcja oraz renderowanie stanu symulacji \label{thread:render} \item Input z sieci (obiór pakietów, kolejkowanie) \label{input:net} \item Input użytkownika (odczyt klawiatury itd.) \label{input:user} - \item Output sieciowy (wysyłanie pakietów, retranmisje) \label{output:net} + \item Output sieciowy (wysyłanie pakietów, retransmisje) \label{output:net} \end{enumerate} +\begin{figure}[H] + \centering + \begin{tikzpicture}[node distance=1cm] + \node[label=below:{Wątek \ref{thread:render}}] (thread-render) { + \begin{tikzpicture}[every node/.style={align=center}, node distance=1cm] + \node[draw] (predict) {Przewidywania}; + \node[draw, below=of predict] (update) {Aktualizacja}; + \node[draw, below=of update] (render) {Wyświetlanie}; + \draw[->] (predict.west) edge [bend right] (update.west) + (update.west) edge [bend right] (render.west) + (render.east) edge [bend right] (predict.east); + \end{tikzpicture} + }; + + \node[label=below:{Wątek \ref{input:net} i \ref{input:user}}, right=of thread-render] (thread-input) { + \begin{tikzpicture}[every node/.style={align=center}, node distance=1cm] + \node[draw] (udp) {Nasłuchiwanie UDP}; + \draw[->] (udp) edge [loop above] (udp); + + \node[draw, below=of udp] (user) {Nasłuchiwanie HID}; + \draw[->] (user) edge [loop above] (user); + \end{tikzpicture} + }; + + \node[label=below:{Wątek \ref{output:net}}, left=of thread-render] (thread-output) { + \begin{tikzpicture}[every node/.style={align=center}, node distance=1cm] + \node[draw] (listen) {Wysyłka pakietów}; + \draw[->] (listen) edge [loop above] (listen); + \end{tikzpicture} + }; + + \node[below=of thread-render] (queue-actions) {Kolejka akcji}; + \node[above=of thread-render] (queue-output) {Kolejka wysyłki}; + + \draw[<->, double] (thread-render) edge (queue-actions) + (thread-input) edge (queue-actions); + + \draw[<->, double] + (thread-output) edge (queue-output) + (thread-input) edge (queue-output); + \end{tikzpicture} + +\end{figure} + Wątki \ref{input:net} oraz \ref{input:user} będą umieszczały akcje do wykonania w jednej kolejce akcji, zatem kolejka ta musi być \textit{thread-safe} i udostępniać atomowe metody \texttt{enqueue} i \texttt{dequeue}, gwarantujące że stan kolejki nie zostanie uszkodzony, tj. żadna akcja nie zostanie wykonana 2-krotnie ani żadna nie zostanie zgubiona. Zostanie to osiągnięte poprzez ustawienie Mutexa\footnote{https://msdn.microsoft.com/en-us/library/system.threading.mutex(v=vs.110).aspx} w metodach. -Zakładamy, że akcje użytkownika muszą zostać zostać umieszczone na kolejce akcji natychmiastowo, zatem nie -ma potrzeby synchronizacji z siecią. Co za tym idzie, dostęp do kolejki pakietów (potrzebnej ze względu na możliwość -otrzymania pakietów w nieodpowiedniej kolejności) i jej obsługa będzie w całości po stronie wątku \ref{input:net} - -czyli problem wyścigów nie następuje. +Zakładamy, że akcje użytkownika muszą zostać umieszczone na kolejce akcji natychmiastowo, zatem nie ma potrzeby +synchronizacji z siecią. Co za tym idzie, dostęp do kolejki pakietów (potrzebnej ze względu na możliwość otrzymania +pakietów w nieodpowiedniej kolejności) i jej obsługa będzie w całości po stronie wątku \ref{input:net} - czyli problem +wyścigów nie następuje. Poprzez identyczną kolejkę będzie obsługiwana wysyłka pakietów, stworzona w celu skomunikowania wątków \ref{input:user} oraz \ref{output:net}. Dodatkowo w wątku \ref{output:net} będzie działać samodzielna kolejka odpowiadająca za retransmisję pakietów. Niektóre pakiety zmieniają stan rozgrywki (np. śmierć, wystrzelenie pocisku), a co za tym idzie muszą zostać -przetworzone przez wszystkich klientów w zbliżonej kolejnosci i w miarę jednakowym czasie (nie ma możliwości -zagwarntowania idealnej synchronizacji). W tym celu wprowadzony zostanie system numerowania kolejnych pakietów a każdy -pakiet, będzie zawierał informację na temat stanu od którego "zależy" - tj. numeru ostatniej aktualizacji. +przetworzone przez wszystkich klientów w zbliżonej kolejności i w miarę jednakowym czasie (nie ma możliwości +zagwarantowania idealnej synchronizacji). W tym celu wprowadzony zostanie system numerowania kolejnych pakietów, a każdy +pakiet będzie zawierał informację na temat stanu od którego ,,zależy'' - tj. numeru ostatniej aktualizacji. \subsection{Protokół} Protokół opiera się o wysyłanie datagramów UDP między wszystkimi klientami. Ze względu na specyfikę UDP (brak gwarancji @@ -236,9 +280,11 @@ ustawiony na 1 nie zachodzi potrzeba retransmisji - czyli są to pakiety mało i Przykładem takiego pakietu może być aktualizacja pozycji gracza - nie ma potrzeby retransmisji ponieważ prawdopodobnie i tak w drodze jest następny pakiet z nowszą pozycją. -W celu zapewnienia poprawności transmisji, każdy pakiet poprzedzany jest 32-bitową sumą kontrolną \texttt{CRC32}, +W celu zapewnienia poprawności transmisji, każdy pakiet poprzedzany jest 16-bitową sumą kontrolną \texttt{CRC16}, pozwalającą upewnić się, że dane zostały odebrane poprawnie. Suma kontrolna liczona jest ze wszystkich składowych -pakietu poczynając od \texttt{Type} włącznie. +pakietu poczynając od \texttt{Type} włącznie. Dodatkowo, jeżeli pakiet potrzebuje dodatkowych danych prócz preambuły to +dane te zawierają własną sumę kontrolną \texttt{CRC16}. Rozgraniczenie to wprowadzono, aby uniknąć sytuacji, w której w +preambule zostałoby uszkodzone pole \texttt{Size} uniemożliwiając pobranie całego pakietu poprawnie. W wypadku niezgodności przesłanej sumy kontrolnej z sumą wyliczoną po stronie odbierającego, odbierający wysyła pakiet \texttt{RET = 0x81}. Dla pakietów mało ważnych nie zachodzi potrzeba retransmisji. @@ -247,7 +293,7 @@ Aby upewnić się, że transmisja ważnego pakietu się powiodła, każdy klient (\texttt{Type = 0x80}). W wypadku, gdyby odpowiedź \texttt{ACK} nie dotarła w ciągu $t\ \si{ms}$ pakiet zostaje wysłany ponownie. -W przypadku wielokrotnego otrzymania pakietu o tym samym \texttt{PacketId} (np. w wypadku automatycznej retramsnisji), +W przypadku wielokrotnego otrzymania pakietu o tym samym \texttt{PacketId} (np. w wypadku automatycznej retransmisji), każdorazowo należy potwierdzić jego otrzymanie odpowiedzią \texttt{ACK} jednak akcja związana z tym pakietem powinna zostać wykonana tylko raz. @@ -286,10 +332,13 @@ Stąd przyjęto, że do realizacji lobby zostanie wykorzystany protokół TCP. Z pośrednika do zestawiania połączeń, dokładny opis działania zdecydowano się pominąć. Ponieważ protokół UDP nie zapewnia obsługi połączeń, każdy klient musi wysłać do wszystkich innych pakiet -\texttt{CONNECT = 0x01}, którego odebranie zgodnie z opisem powyżej (najstarszy bit = 0) musi zostać potwierdzonye. -Po udanym nawiazaniu połączenia ze wszystkimi klientami, zostaje utworzony czołg gracza, a informacja o tym zostaje +\texttt{CONNECT = 0x01}, którego odebranie zgodnie z opisem powyżej (najstarszy bit = 0) musi zostać potwierdzone. +Po udanym nawiązaniu połączenia ze wszystkimi klientami, zostaje utworzony czołg gracza, a informacja o tym zostaje przesłana do pozostałych graczy. +Dokładny opis pakietów służących do aktualizacji stanu rozgrywki (np. aktualizacja pozycji gracza) pomijamy, ponieważ +nie ma związku z problemem synchronizacji klientów. + \section{Potencjalne zmiany} Założono, że pakiety będą dochodziły \textit{wystarczająco szybko} aby desynchronizacja związana z opóźnieniem w ruchu sieciowym nie stanowiła problemu. Jednak należy przeprowadzić testy, czy faktycznie jest to dobre założenie. W wypadku, @@ -299,7 +348,7 @@ opóźnienia odebrania pakietu względem przesłania - np. poprzez wprowadzenie Synchronizację zegara można zapewnić w sposób analogiczny do działania protokołu SNTP\footnote{https://en.wikipedia.org/wiki/Network\_Time\_Protocol}, przy czym synchronizacja następowałaby do zegara \textit{mastera}. W takim wypadku do pakietów moglibyśmy dołączyć informację na temat momentu w rozgrywce, w którym -pakiet został wysłany i odpowiednio uwzględniać opóźnienie (np. przesuwając obiekt o odpowiednia odległość względem +pakiet został wysłany i odpowiednio uwzględniać opóźnienie (np. przesuwając obiekt o odpowiednią odległość względem opóźnienia i dostępnych wektorów prędkości oraz przyspieszenia).