Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë

Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
Përgjigjja për shërbimin, infrastruktura si shërbim, platforma si shërbim, platforma komunikimi si shërbim, videokonferenca si shërbim, por çfarë ndodh me lojërat në re si shërbim? Ka pasur disa përpjekje për të krijuar lojëra në re, siç është Stadia, e lanceruar kohët e fundit nga Google. nuk është një e re në WebRTC, por a mund t'i përdorin të tjerët WebRTC po ashtu?

Thanh Nguyen vendosi të testojë këtë mundësi në projektin e tij open-source CloudRetro. CloudRetro është i bazuar në Pion, një bibliotekë WebRTC popullore e ndërtuar në Go (falë Shonit nga ekipi i zhvilluesve të Pion për ndihmën në përgatitjen e këtij artikulli). Në këtë artikull, Thanh jep një pasqyrë të arkitekturës së projektit të tij, si dhe tregon se çfarë ka mësuar dhe me cilat sfida është përballur gjatë punës.

Hyrje

Vitni që kaloi, kur Google shpalli Stadia, më la pa fjalë. Ideja është aq unike dhe inovative, saqë vazhdimisht më shkonte mendja si është e mundur një gjë e tillë me teknologjitë ekzistuese. Dëshira për ta kuptuar më mirë këtë temë më nxiti të krijoj versionin tim të lojës në cloud me burim të hapur. Rezultati ishte thjesht fantastik. Më poshtë do të doja të ndaja procesin e punës mbi projektin tim njëvjeçar projektin.

TLDR: një version i shkurtër me pika kryesore

Pse e ardhmja është në lojërat në cloud

Unë besoj se Cloud Gaming shpejt do të bëhet gjenerata e re jo vetëm e lojërave, por edhe e fushave të tjera të informatikës. Lojërat në cloud janë maja e modelit klient/server. Ky model maksimizon kontrollin mbi backend dhe minimizimin e punës në frontend duke vendosur logjikën e lojës në një server të largët dhe transmetimin e imazheve/audios klientit. Serveri kryen përpunimin e rëndë, kështu që klienti nuk varet më nga kufizimet harduerike.

Google Stadia, në thelb, lejon të luash lojëra AAA (dmth. lojëra të nivelit të lartë) në një ndërfaqe si YouTube. E njëjta metodologji mund të aplikohet për aplikacione të tjera të rënda offline, si sistemi operativ ose dizajni grafik 2D/3D, etj., që të mund të operojmë vazhdimisht në pajisje me karakteristika të ulëta në platforma të ndryshme.

Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
E ardhmja e kësaj teknologjie: imagjinoni nëse Microsoft Windows 10 do të funksiononte në shfletuesin Chrome?

Lojërat në re janë teknologjikisht të ndërlikuara

Lojërat janë një nga ato fusha të rralla ku kërkohet një reagim i shpejtë dhe të vazhdueshëm nga përdoruesi. Nëse ndonjëherë përballohemi me një vonesë prej 2 sekondash kur klikojmë në një faqe, kjo është e pranueshme. Transmetimet video në kohë reale zakonisht kanë disa sekonda vonesë, por ende ofrojnë komoditet të mjaftueshëm për përdorim. Megjithatë, nëse një lojë ka shpesh vonesa prej 500 ms, atëherë është thjesht e pamundur të luash. Qëllimi ynë është të arrijmë një vonesë jashtëzakonisht të ulët, në mënyrë që distanca midis hyrjes dhe medias të jetë sa më e vogël. Prandaj, qasja tradicionale ndaj video transmetimit nuk është e aplikueshme këtu.

Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
Shablli i përgjithshëm i lojës në re

Projekti me burim të hapur CloudRetro

Vendosa të krijoj një mostër testimi të lojës në re për të provuar nëse gjithçka është e mundur me kufizimet e rrepta të rrjetit. Për të verifikuar konceptin, zgjodha Golang, pasi kjo është gjuha që më njeh më mirë dhe përshtatet mirë për këtë implementim për shumë arsye të tjera, siç bëhet e qartë më vonë. Go është e thjeshtë dhe evoluon shumë shpejt; kanalet në Go janë të shkëlqyera për menaxhimin e shumëprocesorësisë.

Projekt CloudRetro.io – shërbim i lojërave në re me burim të hapur për lojërat retro. Qëllimi i projektit është të sjellë përvojën më të rehatshme të lojës në lojërat tradicionale retro dhe të shtojë multiplayer.
Mund ta njihni projektin më në detaje këtu: https://github.com/giongto35/cloud-game.

Funksionaliteti i CloudRetro

Për të demonstruar të gjitha fuqitë e lojërave në re, në CloudRetro përdoren lojërat retro. Kjo lejon përvojë të shumta unike të lojës.

  • Portabiliteti i lojës
    • Luajtje e menjëhershme kur hapni faqen; ngarkimi dhe instalimi nuk kërkohen
    • Funksionon në shfletuesin mobil, kështu që nuk është e nevojshme asnjë software për ta nisur

  • Sesionet e lojërave mund të ndahen në disa pajisje dhe ruhen në re për hyrjen e ardhshme
  • Lojën mund ta transmetoni dhe mund të luani menjëherë me disa përdorues:
    • Crowdplay si TwitchPlayPokemon, por më shumë platformë dhe më real-time
    • Lojërat offline në internet. Shumë përdorues mund të luajnë pa konfigurimin e rrjetit. Në Samurai Shodown tani mund të luajnë 2 lojtarë në rrjetin CloudRetro

    Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
    Versioni demo i lojës shumë-përdorues në pajisje të ndryshme

    Infrastruktura

    Kërkesat dhe staku i teknologjive

    Më poshtë është lista e kërkesave që kam vendosur para fillimit të projektit.

    1. Një lojtar
    Ky kërkesë mund të duket jo shumë e rëndësishme dhe e evidentuar këtu, por është një nga përfundimet e mia kyçe; kjo lejon lojërat në cloud të qëndrojnë sa më larg shërbimeve tradicionale të transmetimit. Nëse përqendrohemi në lojërat njëpërdorëshe, mund të shpëtojmë nga një server i centralizuar ose CDN, sepse nuk na nevojitet transmetim masiv. Në vend që të ngarkojmë rrjedhat në një server për të përthithur ose të dërgojmë paketa në një server të centralizuar WebSocket, rrjedhat e shërbimit dërgohen drejtpërdrejt te përdoruesi përmes një lidhjeje peer-to-peer WebRTC.

    2. Rrjedha mediale me vonesë të ulët
    Duke lexuar në lidhje me Stadia, shpesh has këtu dhe atje përmendjen e WebRTC-së në disa artikuj. E kuptova se WebRTC është një teknologji e shkëlqyer dhe është e përshtatshme për përdorim në lojërat në cloud. WebRTC është një projekt që i ofron shfletuesve dhe aplikacioneve mobile komunikim në kohë reale përmes një API të thjeshtë. Ai siguron një lidhje peer-to-peer, të optimizuar për media dhe ka kodekët standardë të integruar, si VP8 dhe H264.

    Kam përparësi sigurisë së punës komode të përdoruesve, në vend të ruajtjes së cilësisë së lartë grafikore. Në algoritëm janë të lejuara disa humbje. Në Google Stadia ka një hap shtesë për të ulur madhësinë e imazhit në server, dhe kornizat rriten në cilësi më të lartë para se të transmetohen në nyjat peer-to-peer.

    3. Infrastruktura e distribuar me rrugëzim gjeografik
    Pavarësisht nga sa e optimizuar është algoritmi i kompresimit dhe kodi, rrjeti mbetet faktori kryesor që kontribuon më shumë në vonesë. Arkitektura duhet të ketë një mekanizëm për regjistrimin e serverit më të afërt me përdoruesin për të reduktuar kohën e transmetimit (RTT). Arkitektura duhet të ketë një koordinues dhe disa serverë të transmetimit të shpërndarë në të gjithë botën: Perëndimi i SHBA, Lindja e SHBA, Evropa, Singapori, Kina. Të gjithë serverët e transmetimit duhet të jenë plotësisht të izoluar. Sistemi mund të rregullojë shpërndarjen e tij kur një server i bashkohet ose largohet nga rrjeti. Kështu, në rast të trafikut të lartë, shtimin e serverëve shtesë mundëson përshkallëzim horizontal.

    4. Kompatibiliteti i shfletuesit
    Loja e lojërave në cloud paraqitet në mënyrën më të mirë kur kërkon minimumin nga përdoruesit. Kjo do të thotë se është e mundur ta nisni në shfletues. Shfletuesit ndihmojnë për ta bërë përvojën e lojës sa më të këndshme për përdoruesit, duke i çliruar ata nga instalimi i softuerit dhe harduerit. Shfletuesit gjithashtu ndihmojnë për të siguruar ndërplatformshmërinë për versionet mobile dhe desktop. Fatmirësisht, WebRTC është shumë i mbështetur në shfletues të ndryshëm.

    5. Ndarja e qartë e ndërfaqes së lojës dhe shërbimit
    Unë e konsideroj shërbimin e lojërave në cloud si një platformë. Çdo kush duhet të ketë mundësinë të lidhë çfarëdo në platformë. Tani kam integruar LibRetro me shërbimin e lojërave në cloud, sepse LibRetro ofron një ndërfaqe të këndshme për emulators lojërash për lojërat retro, si SNES, GBA, PS.

    6. Dhoma për multiplayer, crowd play dhe lidhje të thella (deep-link) me lojën
    CloudRetro mbështet shumë lojëra të reja, si CrowdPlay dhe Multiplayer të Online për lojërat retro. Nëse disa përdorues hapin të njëjtin deep-link në kompjuterë të ndryshëm, ata do të shohin të njëjtën lojë të nisur dhe madje do të kenë mundësinë të bashkohen në të.

    Përveç kësaj, shtetet e lojës ruhen në ruajtjen në re. Kjo u lejon përdoruesve të vazhdojnë lojën në çdo kohë në çdo pajisje tjetër.

    7. Shkallëzimi horizontal
    Si çdo SAAS sot, lojërat në re duhet të jenë të projektuar për t'u shkallëzuar horizontalisht. Konstrukti "koordinator-punëtor" lejon shtimin e punëtorëve për të shërbyer më shumë trafik.

    8. Nuk ka lidhje me një re të vetme
    Infrastruktura e CloudRetro është e vendosur në ofrues të ndryshëm të reve (Digital Ocean, Alibaba, ofruesi i personalizuar) për rajone të ndryshme. Aktivizoj startimin në një kontejner Docker për infrastrukturën dhe konfigurimin e parametrave të rrjetit me një skript bash për të shmangur varësinë nga një ofrues i vetëm. Duke e kombinuar këtë me NAT Traversal në WebRTC, ne mund të fitojmë fleksibilitet për të vendosur CloudRetro në çdo platformë në re dhe madje në makinat e çdo përdoruesi.

    Dizajni arkitektonik

    Punëtori: (ose serveri i transmetimi, i përmendur më sipër) shumon lojrat, ekzekuton një pipeline kodimi dhe transmeton mediat e koduara te përdoruesit. Instancat e punonjësve shpërndahen në të gjithë botën, dhe çdo punonjës mund të përpunojë disa seanca përdoruesish në të njëjtën kohë.

    Koordinator: përgjegjës për lidhjen e përdoruesit të ri me punonjësin më të përshtatshëm për transmetim. Koordinator ndërvepron me punonjësit përmes WebSocket.

    Depoja e gjendjeve të lojrave: depo e qendrore e largët për të gjitha gjendjet e lojës. Kjo depo ofron funksione të rëndësishme si ruajtja/ngarkimi i largët.

    Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
    Arkitektura e nivelit të lartë të CloudRetro

    Skema e përdoruesit

    Kur një përdorues i ri hap CloudRetro në hapat 1 dhe 2, të ilustruar më poshtë, koordinatori kërkon së bashku me listën e punonjësve të disponueshëm në faqen e parë. Pas kësaj, në hapin 3, klienti llogarit vonesat për të gjithë kandidatët me një kërkesë HTTP ping. Ky listë vonesash dërgohet përsëri te koordinatori, në mënyrë që ai të mund të përcaktojë punonjësin më të përshtatshëm për të shërbyer përdoruesin. Në hapin 4 më poshtë krijohet një lojë. Një lidhje streaming WebRTC vendoset midis përdoruesit dhe punonjësit të caktuar.
    Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
    Skema e përdoruesit pas marrjes së aksesit

    Çfarë ka brenda punonjësit

    Pipeline-t e lojërave dhe streaming ruhen brenda punonjësit në mënyrë izoluese dhe shkëmbejnë informacion aty përmes një interface. Aktualisht, kjo lidhje bëhet përmes kalimit të të dhënave në memorie përmes kanaleve Golang në të njëjtin proces. Qëllimi i ardhshëm është segregimi, dmth. ekzekutimi i pavarur i lojës në një proces tjetër.

    Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
    Interaksioni i komponentëve të punonjësit

    Pjesët kryesore:

    • WebRTC: komponenti klient, që merr hyrjen e përdoruesit dhe del media të koduar nga serveri.
    • Emulatori i lojërave: komponenti i lojërave. Falë bibliotekës Libretro, sistemi është i aftë të nisë lojën brenda të njëjtit proces dhe të kapë ndërfaqet mediatike dhe rrugët e inputit.
    • Kadat brenda lojës kapen dhe dërgohen në kodues.
    • Koduese e imazheve/audio: një pipeline kodues që merr kadat mediatike, i kodon ato në sfond dhe del imazhe/audio të koduara.

    Realizimi

    CloudRetro mbështetet në WebRTC si teknologjinë kryesore, ndaj para se të zhvillohem në detajet e implementimit në Golang, vendosa të flas për vetë WebRTC-në. Kjo është një teknologji e mrekullueshme që më ndihmoi shumë në arritjen e një vonese të transmetimit të dhënash prej vetëm një pjesë të sekondës.

    WebRTC

    WebRTC është e dizajnuar për të siguruar lidhje të cilësisë së lartë peer-to-peer në aplikacione natyrale mobile dhe në shfletues përmes API-ve të thjeshta.

    Kalimi NAT

    WebRTC është i njohur për funksionalitetin e tij NAT Traversal. WebRTC është krijuar për komunikim peer-to-peer. Qëllimi i tij është të gjejë rrugën më të përshtatshme direkte, duke evituar NAT-in dhe firewalls për komunikimin peer-to-peer nëpërmjet një procesi të quajtur ICE. Në kuadër të këtij procesi, API WebRTC gjen adresën tuaj publike IP përmes serverëve STUN dhe e redirekton atë në serverin e përcjellësit (TURN), kur një lidhje e drejtpërdrejtë nuk mund të krijohet.

    Megjithatë, CloudRetro nuk e përdor plotësisht këtë mundësi. Lidhjet e tij peer-to-peer ekzistojnë jo midis përdoruesve, por midis përdoruesve dhe serverëve në cloud. Ana serverë e modelit ka më pak kufizime për lidhjet e drejtpërdrejta sesa një pajisje zakonore përdoruesi. Kjo lejon hapjen e parakohshme të porteve hyrëse ose përdorimin e adresave IP publike direkt, pasi serveri nuk është nën NAT.

    Disa vite më parë, doja ta transformoja projektin në një platformë shpërndarjeje lojërash për Cloud Gaming. Ideja ishte të lejoja krijuesit e lojërave të ofronin lojëra dhe burime transmetimi. Ndërsa përdoruesit do të bashkëpunonin drejtpërdrejt me ofruesit. Kështu, në një mënyrë të decentralizuar, CloudRetro ishte thjesht një mjedis për lidhjen e burimeve të transmetimit të palëve të treta me përdoruesit, duke e bërë atë më të shkallëzueshëm, kur nuk kishte më një host në të. Roli i WebRTC NAT Traversal është shumë i rëndësishëm për lehtësimin e inicializimit të një lidhjeje peer-to-peer me burimet e transmetimit të palëve të treta, duke e bërë më të lehtë lidhjen e krijuesit me rrjetin.

    Ngjeshja e videos

    Ngjeshja e videos është një pjesë thelbësore e pipeline-it që kontribuon shumë në qetësinë e transmetimit. Megjithëse nuk është e nevojshme të keni njohuri mbi të gjitha detajet e kodimit të videos në VP8/H264, kuptimi i konceptit ndihmon në menaxhimin e parametrave të shpejtësisë së videos në transmetim, zgjidhjen e sjelljeve të papritura dhe rregullimin e vonesave.

    Kompresimi i videove për shërbime streaming është një detyrë e komplikuar, sepse algoritmi duhet të garantojë që koha totale e kodimit + koha e transmetimit në rrjet + koha e dekodimit është sa më e vogël të jetë e mundur. Përveç kësaj, procesi i kodimit duhet të jetë i vazhdueshëm dhe i pandërprerë. Disa prapëvaçësi gjatë kodimit nuk janë të aplikueshme – për shembull, nuk mund të preferojmë një kohë të gjatë kodimi për një madhësi më të vogël skedari dhe kohë dekodimi, ose të përdorim kompresim të paqëndrueshëm.

    Ideja e kompresimit të videove është që të eliminohen bitet e panevojshme të informacionit, duke ruajtur një nivel të pranueshëm saktësie për përdoruesit. Përveç kodimit të kadrove statike individuale të imazhit, algoritmi bën qëndrimin për kadron aktuale nga e kaluara dhe e ardhmja, kështu që dërgohet vetëm dallimi i tyre. Siç shihet në shembullin me Pacman, vetëm piketat diferenciale dërgohen.

    Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
    Krahasimi i videokadrove në shembullin e Pacman

    Kompresimi i audios

    Në të njëjtën mënyrë, algoritmi i kompresimit të zërit heq të dhënat që nuk mund të perceptohen nga njeriu. Opus aktualisht është kodeku audio me performancën më të mirë. Ai është zhvilluar për të transmetuar valët e audios përmes një protokolli të datagramës së renditur, siç është RTP (Protokolli i Transportit në Kohë reale). Vonesa e tij është më e ulët se ajo e mp3 dhe aac, dhe cilësia është më e lartë. Vonesa zakonisht është rreth 5~66.5 ms.

    Pion, WebRTC në Golang

    Pion është një projekt me kod të hapur që sjell WebRTC në Golang. Në vend të përgjithësimit të librarive native C++ të WebRTC, Pion është një implementim native Golang i WebRTC me performancë më të mirë, integrim me Go, si dhe menaxhim versioni për protokollet WebRTC.

    Biblioteka gjithashtu ofron transmetim me shumë module të shkëlqyera të integruara me vonesë më pak se një sekondë. Ajo ka implementimin e saj të STUN, DTLS, SCTP, etj., si dhe disa eksperimente me QUIC dhe WebAssembly. Vetë kjo bibliotekë open-source është një burim vërtet i shkëlqyer për mësim me dokumentacion të shkëlqyer, implementim të protokollit rrjet dhe shembuj të shkëlqyer.

    Komuniteti Pion, drejtuar nga një krijues shumë i pasionuar, është mjaft aktiv, ka shumë diskutime cilësore mbi WebRTC. Nëse jeni të interesuar për këtë teknologji, bashkohuni me http://pion.ly/slack – do të mësoni shumë gjëra të reja.

    Shkrimi i CloudRetro në Golang

    Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
    Implementimi i një punëtori në Go

    Kanalet Go në veprim

    D grazie dizajnit të bukur të kanaleve Go, problemet e transmetimit të ngjarjeve dhe paralelizmit bëhen shumë më të lehta. Siç tregohet në diagram, disa komponentë punojnë paralelisht në GoRoutines të ndryshme. Çdo komponent menaxhon gjendjen e tij dhe komunikon përmes kanaleve. Aftësia selektive e Golang bën që të përpunohet një ngjarje atomike në çdo moment të lojës (game tick). Kjo do të thotë se për një dizajn të tillë nuk nevojitet bllokim. Për shembull, kur një përdorues ruan, kërkohet një snapshot i plotë i gjendjes së lojës. Kjo gjendje duhet të mbetet e vazhdueshme, duke ekzekutuar input deri sa ruajtja të përfundojë. Gjatë çdo game tick’i, backend mund të përpunojë vetëm një operacion ruajtjeje ose inputi, duke e bërë procesin të sigurt për shumëllojshmëri.

    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

    Kyçi Golang është shumë i përshtatshëm për skenarin tim të përdorimit me CrowdPlay dhe Multiple Player. Duke ndjekur këtë model, të gjitha hyrjet e përdoruesve në një dhomë integrohen në kanalin kryesor të hyrjes. Mediat e lojës më pas shpërndahen në të gjithë përdoruesit në një dhomë. Në këtë mënyrë, ne arrijmë ndarjen e gjendjes së lojës midis disa seancave të lojës të përdoruesve të ndryshëm.

    Lojërat në re me burim të hapur në WebRTC: p2p, shumëlojëshe, zero vonesë
    Sinkronizimi midis seancave të ndryshme

    Disavantazhet e Golang

    Golang nuk është i përsosur. Kanali është i ngadalshëm. Në krahasim me bllokimin, kanali Go është thjesht një mënyrë më e lehtë për të trajtuar ngjarjet paralele dhe përfluktuese, por kanali nuk ofron performancën më të mirë. Nën kanal ka një logjikë të komplikuar bllokimi. Prandaj, kam bërë disa korrigjime në zbatimin, duke rishtuar bllokimet dhe vlerat atomike kur zëvendësoj kanalët për optimizimin e performancës.

    Për më tepër, mbledhësi i mbetjeve në Golang është i paqëndrueshëm, duke shkaktuar ndonjëherë shpërthime të dyshimta të gjata. Kjo e pengon ndjeshëm funksionimin e aplikacionit në kohë reale.

    CGO

    Për projektin përdoret një bibliotekë ekzistuese VP8/H264 Golang me burim të hapur për kompresimin e medias dhe Libretro për emuluesit e lojërave. Të gjitha këto biblioteka janë thjesht mbështjellës të bibliotekës C në Go duke përdorur CGO. Disa nga disavantazhet e tyre janë përmendur në këtë postim të Dave Cheney. Problemet me të cilat kërkova:

    • pamundësia për të kapur crash-in në CGO, madje edhe me ndihmën e Golang RecoveryCrash;
    • pamundësia për të identifikuar ngushticat në performancë kur nuk mund të zbulojmë detajet në CGO.

    Përfundimi

    Arrita qëllimin tim - kuptova shërbimet e lojrave cloud dhe krijova një platformë që ndihmon për të luajtur lojërat retro nostalgjike me miqtë e mi online. Krijimi i këtij projekti do të kishte qenë i pamundur pa bibliotekën Pion dhe mbështetje nga komuniteti Pion. Jam jashtëzakonisht mirënjohës për zhvillimin e tij të intensifikuar. API-të e thjeshta të ofruara nga WebRTC dhe Pion ofruan integrim të qetë. Dëshmia ime e parë e konceptit u lëshua në të njëjtën javë, megjithëse nuk dija paraprakisht për lidhjen një-në-një (P2P).

    Megjithëse integrimi është i thjeshtë, transmetimi P2P është një fushë shumë e ndërlikuar në shkencën kompjuterike. Ai përballet me kompleksitetin e arkitekturave shumëvjeçare të rrjetit, si IP dhe NAT, për të krijuar një seancë peer-to-peer. Gjatë punës në këtë projekt kam mbledhur njohuri të çmuara për rrjetin dhe optimizimin e performancës, prandaj rekomandoj të gjithëve që ta provoni të ndërtoni produkte P2P me WebRTC.

    CloudRetro mbulon të gjitha skenarët e përdorimit që prisja, nga këndvështrimi im si retro-gamer. Sidoqoftë, mendoj se ka shumë fusha në projekt që mund t'i përmirësoj, si ta bëj rrjetin më të besueshëm dhe më eficient, të siguroj cilësi më të lartë grafike për lojërat, ose mundësinë për të ndarë lojërat mes përdoruesve. Po punoj shumë në këtë drejtim. Ju lutem, ndihmoni projektin dhe mbështeteni nëse ju pëlqen.

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster