Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie

Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
Usługa jako oprogramowanie, infrastruktura jako usługa, platforma jako usługa, usługa komunikacyjna jako usługa, wideokonferencje jako usługa, a co z grami w chmurze jako usługa? Już podjęto kilka prób stworzenia gier w chmurze (Cloud Gaming), na przykład Stadia, niedawno uruchomiona przez firmę Google. Stadia nie jest nowicjuszem w WebRTC, ale czy inni mogą korzystać z WebRTC w ten sam sposób?

Thanh Nguyen postanowił sprawdzić tę możliwość w swoim otwartym projekcie CloudRetro. CloudRetro oparty jest na Pion, popularnej bibliotece WebRTC napisanej w Go (dzięki Shonowi z zespołu deweloperów Pion za pomoc w przygotowaniu tego artykułu). W artykule Thanh dokonuje przeglądu architektury swojego projektu oraz opowiada, jakie użyteczne informacje uzyskał i z jakimi wyzwaniami się zmierzył podczas pracy.

Wprowadzenie

W zeszłym roku, gdy Google zapowiedziało Stadia, byłem kompletnie zaskoczony. Pomysł jest tak wyjątkowy i innowacyjny, że ciągle zadawałem sobie pytanie, jak to w ogóle możliwe przy istniejących technologiach. Chęć lepszego zrozumienia tego tematu skłoniła mnie do stworzenia własnej wersji otwartej gry w chmurze. Efekt był po prostu fantastyczny. Poniżej chciałbym podzielić się procesem pracy nad moim rocznym projektem.

TLDR: krótka wersja slajdowa z najważniejszymi punktami

Dlaczego przyszłość leży w grach w chmurze

Wierzę, że Cloud Gaming wkrótce stanie się nowym pokoleniem nie tylko gier, ale i innych dziedzin informatyki. Gry w chmurze to szczyt modelu klient/serwer. Taki model maksymalizuje kontrolę nad backendem i minimalizuje pracę frontendową poprzez umieszczenie logiki gry na zdalnym serwerze i strumieniowe przesyłanie obrazu/dźwięku do klienta. Serwer wykonuje ciężkie obliczenia, więc klient nie jest już zależny od ograniczeń sprzętowych.

Google Stadia w zasadzie pozwala grać w gry AAA (tzn. wysokiej jakości gry blokbusterowe) w interfejsie podobnym do YouTube. Ta sama metodologia może być zastosowana do innych wymagających aplikacji offline, takich jak system operacyjny czy projektowanie graficzne 2D/3D, aby móc stabilnie uruchamiać je na urządzeniach o niskich parametrach technicznych na różnych platformach.

Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
Przyszłość tej technologii: wyobrażacie sobie, gdyby Microsoft Windows 10 działał w przeglądarce Chrome?

Gry w chmurze są technicznie złożone

Gaming is one of the few areas where a constant fast user reaction is required. If we occasionally experience a two-second delay when clicking on a page, that is acceptable. Live video streams generally lag a few seconds but still offer sufficient usability. However, if a game frequently lags by 500 ms, playing becomes nearly impossible. Our goal is to achieve extremely low latency so that the gap between input and media is as small as possible. Therefore, the traditional approach to streaming video is not applicable here.

Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
Ogólny szablon gier w chmurze

Projekt open source CloudRetro

Postanowiłem stworzyć testowy prototyp gry w chmurze, aby sprawdzić, czy jest to możliwe przy tak rygorystycznych ograniczeniach sieciowych. Do poweryfikacji koncepcji wybrałem Golang, ponieważ jest to najbardziej znany mi język i dobrze pasuje do realizacji z wielu innych powodów, co później się okazało. Go jest prosty i szybko się rozwija; kanały w Go doskonale nadają się do zarządzania wielowątkowością.

Projekt CloudRetro.io – chmurowa usługa gier z otwartym kodem źródłowym dla gier retro. Celem projektu jest przynieść do tradycyjnych gier retro najbardziej komfortowe doświadczenia w grze i dodać tryb wieloosobowy.
Szczegóły projektu można znaleźć tutaj: https://github.com/giongto35/cloud-game.

Funkcjonalność CloudRetro

Aby zademonstrować całą moc gier w chmurze, CloudRetro wykorzystuje gry retro. Co pozwala na uzyskanie wielu unikalnych wrażeń z gry.

  • Przenośność gry
    • Natychmiastowe odtwarzanie po otwarciu strony; pobieranie i instalacja nie są potrzebne
    • Działa w mobilnej przeglądarce, więc do uruchomienia nie jest potrzebne żadne oprogramowanie

  • Sesje gier można współdzielić na wielu urządzeniach i przechowywać w chmurze na następne logowanie
  • Grę można streamować, a kilka użytkowników może w nią grać jednocześnie:
    • Crowdplay stylu TwitchPlayPokemon, tylko bardziej międzyplatformowy i bardziej w czasie rzeczywistym
    • Offline gry online. Wiele osób może grać bez konfiguracji sieci. W Samurai Shodown teraz można grać 2 graczy przez sieć CloudRetro

    Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
    Demo wieloosobowej gry online na różnych urządzeniach

    Infrastruktura

    Wymagania i stos technologii

    Poniżej znajduje się lista wymagań, które ustanowiłem przed rozpoczęciem projektu.

    1. Jeden gracz
    To wymaganie może wydawać się mało istotne i oczywiste, ale to jeden z moich kluczowych wniosków – pozwala to grom gromkim grom nadążać z daleka od tradycyjnych usług strumieniowych. Jeśli skupimy się na grze dla jednego gracza, możemy pozbyć się scentralizowanego serwera lub CDN, ponieważ nie potrzebujemy przesyłać strumieni do mas. Zamiast przesyłać strumienie na chłonny serwer lub przekazywać pakiety na scentralizowany serwer WebSocket, strumienie usługi są przesyłane bezpośrednio do użytkownika przez połączenie peer-to-peer WebRTC.

    2. Strumień mediów o niskim opóźnieniu
    Czytając o Stadia, często napotykam w niektórych artykułach wzmianki o WebRTC. Zrozumiałem, że WebRTC to wybitna technologia i doskonale nadaje się do wykorzystania w grach w chmurze. WebRTC to projekt, który zapewnia przeglądarkom internetowym i aplikacjom mobilnym komunikację w czasie rzeczywistym przez prosty interfejs API. Oferuje połączenie peer-to-peer, zoptymalizowane dla mediów, i ma wbudowane standardowe kodeki, takie jak VP8 i H264.

    Skupiłem się na zapewnieniu jak najbardziej komfortowej pracy dla użytkowników, a nie na zachowaniu wysokiej jakości grafiki. W algorytmie dopuszczalne są pewne straty. W Google Stadia jest dodatkowy krok zmniejszający rozmiar obrazu na serwerze, a klatki są skalowane do wyższej jakości przed przekazaniem peer-to-peer.

    3. Rozproszona infrastruktura z geograficznym routowaniem
    Bez względu na to, jak bardzo zoptymalizowany jest algorytm kompresji i kod, sieć nadal stanowi kluczowy czynnik wpływający na opóźnienie. Architektura powinna mieć mechanizm sparowania najbliższego serwera do użytkownika w celu skrócenia czasu odbioru (RTT). Architektura powinna mieć jednego koordynatora i kilka serwerów strumieniowych rozmieszczonych na całym świecie: Zachodnia USA, Wschodnia USA, Europa, Singapur, Chiny. Wszystkie serwery strumieniowe powinny być całkowicie izolowane. System może dostosowywać swoje rozmieszczenie, gdy serwer dołącza do sieci lub z niej wychodzi. W ten sposób, w przypadku dużego ruchu, dodanie dodatkowych serwerów umożliwia poziome skalowanie.

    4. Kompatybilność przeglądarki
    Gry w chmurze pokazują się w najlepszym świetle, gdy wymagają od użytkowników jak najmniej. Oznacza to, że istnieje możliwość uruchamiania ich w przeglądarce. Przeglądarki pomagają uczynić rozgrywkę jak najbardziej komfortową dla użytkowników, eliminując potrzebę instalacji oprogramowania i sprzętu. Przeglądarki również zapewniają możliwość grania na różnych platformach, zarówno mobilnych, jak i desktopowych. Na szczęście WebRTC jest doskonale wspierane w różnych przeglądarkach.

    5. Wyraźny podział interfejsu gry i usługi
    Postrzegam usługę gier w chmurze jako platformę. Każdy powinien mieć możliwość podłączenia do platformy czegokolwiek. Obecnie zintegrowałem LibRetro z usługą gier w chmurze, ponieważ LibRetro oferuje piękny interfejs emulatora gier retro dla takich gier jak SNES, GBA, PS.

    6. Pokoje do multiplayera, crowd play oraz zewnętrzne linkowanie (deep-link) do gry
    CloudRetro wspiera wiele nowych trybów gry, takich jak CrowdPlay i Online MultiPlayer dla gier retro. Jeśli kilku użytkowników otworzy ten sam deep-link na różnych komputerach, zobaczą tę samą uruchomioną grę i będą mogli do niej dołączyć.

    Co więcej, stany gier są przechowywane w chmurze. Umożliwia to użytkownikom kontynuowanie gry w dowolnym momencie na dowolnym innym urządzeniu.

    7. Poziome skalowanie
    Podobnie jak każde SAAS w dzisiejszych czasach, gry w chmurze muszą być zaprojektowane tak, aby były poziomo skalowalne. Konstrukcja „koordynator-pracownik” pozwala na dodawanie kolejnych pracowników, aby obsługiwać większy ruch.

    8. Brak uzależnienia od jednego chmury
    Infrastruktura CloudRetro jest hostowana u różnych dostawców chmury (Digital Ocean, Alibaba, dostawca dedykowany) dla różnych regionów. Aktywuję uruchamianie w kontenerze Docker dla infrastruktury i konfiguruję parametry sieciowe za pomocą skryptu bash, aby uniknąć uzależnienia od jednego dostawcy chmury. Łącząc to z NAT Traversal w WebRTC, możemy uzyskać elastyczność w uruchamianiu CloudRetro na dowolnej platformie chmurowej, a nawet na maszynach jakiegokolwiek użytkownika.

    Projekt architektoniczny

    Pracownik: (lub serwer strumieniowy, wspomniany powyżej) rozdziela gry, uruchamia proces kodowania i przesyła zakodowane media użytkownikom. Instance robocze są rozmieszczone na całym świecie, a każdy robotnik może obsługiwać kilka sesji użytkowników jednocześnie.

    Koordynator: odpowiada za przyporządkowanie nowego użytkownika do najbardziej odpowiedniego robota do strumieniowania. Koordynator komunikuje się z robotnikami za pośrednictwem WebSocket.

    Przechowalnia stanów gier: centralne zdalne przechowywanie dla wszystkich stanów gry. Przechowalnia ta zapewnia tak istotne funkcje, jak zdalne zapisywanie/ładowanie.

    Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
    Architektura CloudRetro na wysokim poziomie

    Scenariusz użytkownika

    Kiedy nowy użytkownik otwiera CloudRetro, w krokach 1 i 2 pokazanych na poniższym rysunku, koordynator jest zapytywany wraz z listą dostępnych robotników na pierwszej stronie. Następnie, w kroku 3, klient oblicza opóźnienia dla wszystkich kandydatów za pomocą żądania HTTP ping. Ta lista opóźnień jest następnie wysyłana z powrotem do koordynatora, aby mógł określić najbardziej odpowiedniego robota do obsługi użytkownika. W kroku 4 poniżej gra jest tworzona. Między użytkownikiem a przypisanym robotnikiem ustanawiane jest strumieniowe połączenie WebRTC.
    Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
    Scenariusz użytkownika po uzyskaniu dostępu

    Co znajduje się w robocie

    Pipeline gier i strumieniowania są przechowywane w robocie w izolacji i wymieniają się informacjami przez interfejs. Obecnie ta komunikacja odbywa się poprzez przesył danych w pamięci przez kanały Golang w tym samym procesie. Następnym celem jest segregacja, tj. niezależne uruchamianie gry w innym procesie.

    Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
    Interakcja komponentów robota

    Główne składniki:

    • WebRTC: komponent kliencki, który przyjmuje wejście użytkownika i wyprowadza zakodowane media z serwera.
    • Emulator gier: komponent gier. Dzięki bibliotece Libretro system jest w stanie uruchamiać grę w tym samym procesie i wewnętrznie przechwytywać media i strumień wejściowy.
    • Klatki wewnątrz gry są przechwytywane i wysyłane do kodera.
    • Koder obrazu/audio: pipeline kodujący, który przyjmuje klatki mediów, koduje je w tle i wyprowadza zakodowane obrazy/audio.

    Realizacja

    CloudRetro polega na WebRTC jako technologii bazowej, dlatego zanim zanurzę się w szczegóły implementacji w Golang, postanowiłem opowiedzieć o samej technologii WebRTC. To niesamowita technologia, która bardzo pomogła mi osiągnąć opóźnienie transmisji danych wynoszące zaledwie ułamki sekundy.

    WebRTC

    WebRTC jest zaprojektowany, aby zapewniać wysokiej jakości połączenia peer-to-peer w natywnych aplikacjach mobilnych oraz w przeglądarkach za pomocą prostych API.

    NAT Traversal

    WebRTC jest znany ze swojej funkcjonalności NAT Traversal. WebRTC jest przeznaczone do komunikacji peer-to-peer. Jego celem jest znalezienie najbardziej odpowiedniej bezpośredniej trasy, unikając bram NAT i zapór ogniowych w procesie zwanym ICE. W ramach tego procesu API WebRTC odnajdują Twój publiczny adres IP za pomocą serwerów STUN i przekierowują go na serwer retransmisji (TURN), gdy nie ma możliwości nawiązania bezpośredniego połączenia.

    Jednak CloudRetro nie wykorzystuje tej funkcji w pełni. Jego połączenia peer-to-peer istnieją nie między użytkownikami, a między użytkownikami a serwerami w chmurze. Część serwerowa modelu ma mniej ograniczeń w bezpośredniej komunikacji niż zwykłe urządzenie użytkownika. Umożliwia to wstępne otwieranie przychodzących portów lub bezpośrednie korzystanie z publicznych adresów IP, ponieważ serwer nie znajduje się za NAT.

    Kiedyś chciałem przekształcić projekt w platformę dystrybucji gier dla Cloud Gaming. Pomysł polegał na tym, aby pozwolić twórcom gier udostępniać gry i zasoby strumieniowe. Użytkownicy mogliby bezpośrednio wchodzić w interakcje z dostawcami. W taki zdecentralizowany sposób CloudRetro jest jedynie środowiskiem do łączenia zewnętrznych zasobów strumieniowych z użytkownikami, co sprawia, że jest bardziej skalowalne, gdy nie jest już obciążone hostingiem. Rola NAT Traversal w WebRTC ma tutaj kluczowe znaczenie dla ułatwienia inicjacji połączenia peer-to-peer z zewnętrznymi zasobami strumieniowymi, co ułatwia podłączenie twórcy do sieci.

    Kompresja wideo

    Kompresja wideo jest nieodłączną częścią procesu, która znacząco przyczynia się do płynności strumienia. Chociaż nie jest konieczne znać wszystkie szczegóły kodowania wideo w VP8/H264, zrozumienie koncepcji pomaga w zrozumieniu parametrów prędkości strumieni wideo, debugowaniu nieoczekiwanego zachowania oraz dostosowywaniu opóźnienia.

    Kompresja wideo dla serwisu streamingowego jest trudnym zadaniem, ponieważ algorytm musi zapewnić, że całkowity czas kodowania + czas transmisji przez sieć + czas dekodowania jest jak najkrótszy. Dodatkowo proces kodowania musi być spójny i ciągły. Niektóre wzajemne ustępstwa w kodowaniu nie są możliwe – na przykład nie możemy preferować długiego czasu kodowania kosztem mniejszego rozmiaru pliku i czasu dekodowania lub stosować niespójnej kompresji.

    Idea kompresji wideo polega na wykluczeniu zbędnych bitów informacji, zachowując jednocześnie akceptowalny poziom dokładności dla użytkowników. Oprócz kodowania pojedynczych statycznych klatek obrazu, algorytm wytwarza dane dla aktualnej klatki na podstawie poprzedniej i następnej, dlatego przekazywana jest tylko ich różnica. Jak pokazuje przykład z Pacmanem, przesyłane są tylko punkty różnicowe.

    Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
    Porównanie klatek wideo na przykładzie Pacmana

    Kompresja audio

    Podobnie, algorytm kompresji dźwięku pomija dane, które nie mogą być postrzegane przez człowieka. Opus jest obecnie kodekiem audio o najlepszej wydajności. Został zaprojektowany do przesyłania fali dźwiękowej przez protokół uporządkowanych datagramów, taki jak RTP (Real Time Transport Protocol – protokół przesyłania danych w czasie rzeczywistym). Jego opóźnienie jest krótsze niż w przypadku mp3 i aac, a jakość wyższa. Opóźnienie wynosi zazwyczaj około 5~66,5 ms.

    Pion, WebRTC w Golang

    Pion to projekt z otwartym kodem źródłowym, który implementuje WebRTC w Golang. Zamiast standardowego owija w natywne biblioteki C++ WebRTC, Pion jest natywną implementacją WebRTC w Golang z lepszą wydajnością, integracją z Go oraz kontrolą wersji na protokołach WebRTC.

    Biblioteka również zapewnia przesyłanie strumieniowe z wieloma doskonałymi wbudowanymi modułami z opóźnieniem poniżej sekundy. Ma własną implementację STUN, DTLS, SCTP itd. oraz eksperymenty z QUIC i WebAssembly. Ta otwartoźródłowa biblioteka jest naprawdę dobrym źródłem nauki z doskonałą dokumentacją, implementacją protokołów sieciowych i świetnymi przykładami.

    Społeczność Pion, kierowana przez bardzo pasjonującego twórcę, jest dość ożywiona, odbywa się tam wiele wartościowych dyskusji o WebRTC. Jeśli interesuje cię ta technologia, dołącz do http://pion.ly/slack – dowiesz się wielu nowych rzeczy.

    Pisanie CloudRetro w Golang

    Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
    Implementacja workera w Go

    Kanały Go w akcji

    Dzięki pięknemu projektowi kanałów Go, problemy z przesyłaniem strumieniowym zdarzeń i równoległością są znacznie uproszczone. Jak pokazano na diagramie, w różnych GoRoutines równolegle działają różne komponenty. Każdy komponent zarządza swoim stanem i komunikuje się za pomocą kanałów. Wybrane stwierdzenie Golang wymusza przetwarzanie jednego atomowego zdarzenia w każdej chwili czasu w grze (game tick). To oznacza, że dla takiego projektu blokada nie jest potrzebna. Na przykład, gdy użytkownik zapisuje, wymagany jest pełny zrzut stanu gry. Ten stan musi pozostać ciągły, wykonując wejście, aż zapis zostanie ukończony. Podczas każdego game tick’a backend może obsługiwać tylko operację zapisu lub wejścia, co czyni proces bezpiecznym dla wątków.

    func (e *gameEmulator) gameUpdate() {
    for {
    	select {
    		case <-e.saveOperation:
    			e.saveGameState()
    		case key := <-e.input:
    			e.updateGameState(key)
    		case <-e.done:
    			e.close()
    			return
    	}
        }
    }

    Fan-in / Fan-out

    Ten wzór Golang doskonale pasuje do mojego scenariusza użycia CrowdPlay i Multiplayer. Podążając za tym wzorem, wszystkie wejścia użytkowników w jednym pomieszczeniu są wbudowywane w centralny kanał wejściowy. Media gry są następnie rozpowszechniane wśród wszystkich użytkowników w jednym pomieszczeniu. W ten sposób osiągamy podział stanu gry między różnymi sesjami gier różnych użytkowników.

    Chmurowe granie z otwartym kodem źródłowym na WebRTC: p2p, multiplayer, zerowe opóźnienie
    Synchronizacja między różnymi sesjami

    Wady Golang

    Golang nie jest doskonały. Kanał jest wolny. W porównaniu do blokady kanał Go to po prostu prostszy sposób obsługi równoległych i strumieniowych zdarzeń, ale kanał nie zapewnia najlepszej wydajności. Pod kanałem kryje się skomplikowana logika blokady. Dlatego wprowadziłem pewne poprawki w realizacji, ponownie stosując blokady i atomowe wartości przy zamianie kanałów dla optymalizacji wydajności.

    Ponadto, garbage collector w Golang jest nieprzewidywalny, co czasami prowadzi do podejrzanie długich przerw. To poważnie przeszkadza w działaniu aplikacji strumieniowej w czasie rzeczywistym.

    CGO

    W projekcie używana jest istniejąca biblioteka VP8/H264 Golang o otwartym kodzie źródłowym do kompresji mediów oraz Libretro do emulatorów gier. Wszystkie te biblioteki to po prostu opakowania biblioteki C w Go z wykorzystaniem CGO. Niektóre z wymienionych wad to ten post Dave'a Cheney'a. Problemy, z którymi się zmierzyłem:

    • niemożność uchwycenia awarii w CGO, nawet przy użyciu Golang RecoveryCrash;
    • niemożność zidentyfikowania wąskiego gardła wydajności, gdy nie możemy wykrywać szczegółowych problemów w CGO.

    Podsumowanie

    Osiągnąłem swój cel – zrozumiałem usługi gier w chmurze i stworzyłem platformę, która pomaga grać w nostalgiczne gry retro z moimi przyjaciółmi online. Stworzenie tego projektu byłoby niemożliwe bez biblioteki Pion i wsparcia społeczności Pion. Jestem niesamowicie wdzięczny za jej intensywny rozwój. Proste interfejsy API oferowane przez WebRTC i Pion zapewniły płynną integrację. Moje pierwsze dowody koncepcji zostały wydane w tym samym tygodniu, mimo że wcześniej nie znałem się na połączeniach typu peer-to-peer (P2P).

    Mimo prostoty integracji, P2P streaming naprawdę jest bardzo skomplikowanym obszarem w informatyce. Musi radzić sobie ze złożonością wieloletnich architektur sieciowych, takich jak IP i NAT, aby stworzyć sesję peer-to-peer. Pracując nad tym projektem, zdobyłem wiele cennych informacji na temat sieci i optymalizacji wydajności, dlatego polecam wszystkim spróbować budować produkty P2P z pomocą WebRTC.

    CloudRetro obsługuje wszystkie scenariusze użycia, których się spodziewałem jako retro-gracz. Niemniej jednak uważam, że istnieje wiele obszarów w projekcie, które mogę poprawić, na przykład uczynić sieć bardziej niezawodną i wydajną, zapewnić wyższą jakość grafiki gier, czy umożliwić dzielenie się grami między użytkownikami. Ciężko pracuję nad tym. Proszę, śledźcie projektem i wspierajcie go, jeśli Wam się podoba.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster