Sprawozdanie: final
This commit is contained in:
parent
f7328d3102
commit
1e03a2929c
@ -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).
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user