Sprawozdanie: RC

This commit is contained in:
Kacper Donat 2018-04-15 20:15:36 +02:00
parent c792f9fa86
commit f7328d3102

View File

@ -93,20 +93,29 @@
Gracze poruszają się po wspólnej planszy czołgami strzelając do siebie i starając się zniszczyć. Każdy gracz kontroluje
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
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.
Dodatkowo, każdy obiekt na mapie zawiera swój własny stan, zależny od typu obiektu. Przykładowo dla czołgu może to być
pozycja (\texttt{vec2}), kąt (\texttt{double}) oraz ich pierwsza oraz druga pochodna (w celach predykcji).
\section{Wykorzystane technologie}
\begin{itemize}
\item C\#
\item SFML.net
\item System.Threading
\item System.Net.Sockets
\item SFML.net - obsługa grafiki oraz wejścia od użytkownika
\item System.Threading - wątki
\item System.Net.Sockets - obsługa sieci
\end{itemize}
\section{Proponowana Architektura}
Z założenia, rozgrywka ma odbywać się w czasie rzeczywistym zatem wykorzystanie protokołu \textit{TCP} mogłoby
wprowadzać niepotrzebne opóźnienia wynikające z niepotrzebnych retransmisji pakietów. Komunikacja będzie odbywała się za
pomocą datagramów UDP, które pozwalają na szybszą - acz mniej dokładną transmisję informacji pomiędzy klientami. W
wypadku normlanej rozgrywki jednak strata części pakietów jest jak najbardziej akceptowalna i nie wpłynie na rozgrywkę w
znaczący sposób.
Z założenia, rozgrywka ma odbywać się w czasie rzeczywistym zatem wykorzystanie protokołu TCP mogłoby
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
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
@ -114,56 +123,6 @@ stan symulacji \textit{mastera}. \textit{masterem} zawsze jest klient o najmniej
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.
Dodatkowo, przyjmuje się zasadę, że każdy klient jest w 100\% odpowiedzialny za swoją część symulacji, dla której
stanowi źródło prawdy. tj. 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ć
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.
Każdy klient będzie obsługiwany przez 4 osobnych wątki, których kompetencje nie kolidują ze sobą:
\begin{enumerate}
\item Aktualizacja i 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}
\end{enumerate}
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.
Poprzez identyczną kolejkę będzie obsługiwana wysyłka pakietów będzie obsługiwana przez identyczną kolejkę 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 jednakowej 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. W wypadku
gdyby doszły pakiety konkurujące ze sobą (tj. o tym samym numerze pakietu, oraz zależące od tego samego stanu) wygrywa
ten, który przyszedł pierwszy ponieważ są one od siebie niezależne.
\subsection{Protokół}
Protokół opiera się o wysyłanie datagramów UDP między wszystkimi klientami. Ze względu na specyfikę UDP (brak gwarancji
dotarcia pakietów, brak gwarancji kolejności, brak gwarancji poprawności) przyjmuje się następujące założenia:
\begin{itemize}
\item nie wszystkie pakiety są ważne, niektóre można pominąć
\item wszystkie pakiety aby zostać przetworzone muszą dojść w całości poprawnie
\item pakiety są numerowane
\item pakiety mogą wymagać konkretnego poziomu stanu gry
\end{itemize}
\begin{figure}[H]
\centering
@ -183,6 +142,63 @@ dotarcia pakietów, brak gwarancji kolejności, brak gwarancji poprawności) prz
\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ć
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,
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
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ą:
\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}
\end{enumerate}
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.
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.
\subsection{Protokół}
Protokół opiera się o wysyłanie datagramów UDP między wszystkimi klientami. Ze względu na specyfikę UDP (brak gwarancji
dotarcia pakietów, brak gwarancji kolejności, brak gwarancji poprawności) przyjmuje się następujące założenia:
\begin{itemize}
\item nie wszystkie pakiety są ważne, niektóre można pominąć
\item wszystkie pakiety aby zostać przetworzone muszą dojść w całości poprawnie
\item pakiety są numerowane
\item pakiety mogą wymagać konkretnego poziomu stanu gry
\end{itemize}
Ze względu na konieczność szybkiego przesyłania informacji pomiędzy klientami, zaprojektowany protokół jest protokołem
binarnym, o konstrukcji pakietu widocznej poniżej.
@ -253,6 +269,13 @@ wzgledu na to, że \texttt{ACK} nie jest bezpośrednio związany z rozgrywką, w
\caption{Pakiet \texttt{ACK}}
\end{figure}
W momencie kiedy po $n$-tej retransmisji pakietu z rzędu nie otrzymamy potwierdzenia \texttt{ACK} uznajemy, że nastąpiło
zerwanie połączenia ze strony tego gracza - dodatkowo, jeżeli jakimś cudem wszyscy pozostali gracze stracili połączenie
zakładamy, że to problem jest po naszej stronie.
W celu sprawdzenia poprawności połączenia z danym graczem można również wysłać pakiet \texttt{PING = 0x00}, za poprawny
\textit{pong} uznajemy pakiet \texttt{ACK}.
\subsection{Zestawienie połączeń}
Ze względu na to, że port na którym nasłuchują klienci nie jest deterministyczny istnieje konieczność wprowadzania
scentralizowanego \textit{lobby} pozwalającego na wymianę tych informacji pomiędzy klientami. Jedynym zadaniem
@ -262,8 +285,22 @@ działania.
Stąd przyjęto, że do realizacji lobby zostanie wykorzystany protokół TCP. Ze względu na to, że lobby stanowi jedynie
pośrednika do zestawiania połączeń, dokładny opis działania zdecydowano się pominąć.
\section{Rozwiązania}
O nie w tym wypadku też ciężko
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
przesłana do pozostałych graczy.
\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,
gdyby jednak desynchronizacja związana z opóźnieniami stanowiła problem należy wprowadzić system pozwalający uwzględniać
opóźnienia odebrania pakietu względem przesłania - np. poprzez wprowadzenie wspólnego, synchronizowanego zegara gry.
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
opóźnienia i dostępnych wektorów prędkości oraz przyspieszenia).
\end{document}