Sprawozdanie: final

This commit is contained in:
Kacper Donat 2018-04-17 21:39:50 +02:00
parent f7328d3102
commit 1e03a2929c

View File

@ -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).