A Source motorra épülõ multiplayer játékok kliens/szerver halózati felépítésûek. Nagy általánosságban elmondható, hogy a játék dedikált szervereken zajlik, és ezek önállóságot élveznek a motort használó játék típusa, annak beállításai, szabályai, és az oda csatlakozó kliensek (játékosok) kezelését illetõen. Ezek a kliensek a játékosok számítógépein futó programok, melyek kapcsolódnak a szerverhez, és a kettejük között zajló kommunikáció kis adatcsomagok nagy gyakoriságú küldésébõl és fogadásából áll (20-30 csomag másodpercenként). Ebben a kétirányú adatközlésben a szerver folyamatosan frissíti az adott játékkal kapcsolatos adatokat a kliensnek, amiket az audio és video jelekké alakít át, valamint a kliens is folyamatosan frissíti a szerver irányába a perifériákról (billentyûzet, egér, mikrofon, stb.) mintavételezett változásokat, és küldi mindezt el a szervernek további feldolgozásra. A kliensek csak és kizárólag a szerverrel folytatják ezt a társalgást, egymással nem állnak kapcsolatban. Ellentétben az egyszemélyes vagy single (offline) játékkal, egy multiplayer játéknak éppen ezért, számos új problémával kell megbirkóznia, amit az eddig ecsetelt csomag alapú adatcsere okoz.
Mivel bármely használt hálózat sávszélessége korlátozott, a szerver nem engedheti meg magának, hogy a játékban bekövetkezõ változásokat rögtön és külön-külön frissítse minden egyes kliensnek, ezért ehelyett a szerver megadott idõközönként pillanatképeket készít a játék menetérõl, és ezeket küldi el. A hálózati csomagoknak idõbe telik, míg megteszik a szerver és a kliens közötti távolságot (lásd: ping) és mindez azt is jelenti, hogy a kliensnek a megkapott információk alapján felépített aktuális jatékállapota, rendre egy kis késésben lesz a szerverhez képest.
Természetesen mindez igaz a kliens oldalról érkezõ csomagokra is, tehát a szerver is késleltetett kliens oldali parancsokat és állapotjelzéseket dolgoz fel. Ráadásul a kliensek késése a szerverhez rendre más és más, ami (a hálózati kapcsolatok típusából eredõ különbségeken felül) az idõvel is változik, függõen a további hálózati forgalmától, vagy a kliens framerate-jétõl is. Ezen egyidejûség-különbségek a szerver és a kliensek között, értelemszerûen vetnek fel problémakat, melyek csak rosszabbak lesznek ezen kétoldalú késés növekedésével.
Jellemzõen a gyors menetû, akció játékokban a legkisebb, néhány ezredmásodperces késés is "laggos" játékélményt okozhat, nehezebbé teszi a másik játékos eltalálását vagy a mozgó tárgyakkal történõ manipulációt (hisz máshol vannak valójában, mint amit látunk. a szerk.meg.). A sávszélesség korlátozása vagy a nagy mértékû késés, könnyen okozhat információvesztést a kommunikációban, köszönhetõen, a hálózati csomagok eltûnésének vagy elévülésének.
Hogy mindezekkel a hálózati kommunikációval kapcsolatos, fentebb tárgyalt problémákkal megbirkózzon, a Source motorja több módszerrel küszöböli ki, vagy legalábbis próbálja kevésbe szembetûnõvé tenni a játékosok számára mindezt. Ezen módszerek az adat kompresszió (tömörítés), interpoláció (kitöltés), predikció (elõrejelzés/várakozás) és lag kompenzáció (kiegyenlítés). Mindezen eszközök szorosan kötõdnek egymáshoz, és valamely változtatás valamelyikben komoly befolyással lehet a többire. Ez a dokumentum nagy általánosságban írja le ezen rendszerek mûködését, funkcióját és egymáshoz való viszonyát.
Az alapok
A szerver az adott játék szimulációját nem folyamatosan, hanem diszkrét változások/változtatásokon keresztül végzi, ez a játék szívverése, és mostantól tick néven hivatkozunk rá. Alapértelmezésben a motor 66 ticket szimulál másodpercenként, de a különbözõ modok saját beállításokat használhatnak. Például a Counter-strike: Source alacsonyabb, 33-as értekkel dolgozik, hogy így is csökkentse a szerver CPU terhelését. Minden szívdobbanás, azaz tick után, a játék beállításaival összhangban, a szerver feldolgozza a kliens oldalról kapott parancsokat és ezek együttese alapján pedig szimulálja a objektumok változását, befrissíti azokat. Miután a szerver lejátszotta magában ezt a szívverést, ellenõrzi, hogy mely kliensekkel szükséges változásokat közölni, és ezek alapján eldönti, hogy a szükséges pillanatképet megcsinálja-e. A szimuláció pontossága értelemszerûen a tickrate növekedésével nõ, ugyanakkor mind a kliens, mind a szerver oldal sávszélességével valamint a szerver CPU-jával szemben is nagyobb követelményeket támaszt. A szerver adminja tetszés szerint állíthatja a szerver szívverését a -tickrate futtatási paraméterrel, de nem feltétlenül szerencsés, mert a motor adott modja nem feltétlenül fog a tervezett módon mûködni a módosított tickrate-tel.
Általában a kliensek rendelkezésére álló sávszélessége korlátozott. Egy modemes kapcsolat esetén a kliens által kapható maximális adatmennyiség 5-7 KB/másodperc, csak hogy a legrosszabb esetet említsük. Amennyiben a szerver nagyobb mennyiségû adatot próbál közölni mint amennyit a kliens befogadni képes, a csomagvesztés elkerülhetetlen. Éppen ezért a rate(bytes/másodperc) változó megfelelõ beállításával, minden egyes kliensnek meg kell mondania a szervernek, hogy az õ személyes kapacitása a csomagok fogadására mekkora. Ez a legfontosabb kliens oldali hálózati beállítás, melynek pontos eltalálása elengedhetetlen az optimális játékélményhez. A kliens szintén megadhatja, a cl_updaterate paranccsal, hogy másodpercenként maximum hány pillanatképet, csomagot hajlandó fogadni a szervertõl, de a szerver soha nem fog többet küldeni az általa legenerált szívverésekbõl, mint amennyit a rate parancs által megadott sávszélességkorlát lehetõvé tesz, és természetesen szintén korlátja az, hogy maga a szerver hány ilyen szívverést generál le, tehát hogy mekkora tickrate-en fut. A szerver üzemeltetõje korlátozhatja a maximálisan elküldhetõ adatokat a már említett bontásban, tehát a maximális kliens oldalra nyíló sávszélességet az sv_maxrate, és a maximimalis lefrissítés gyakoriságát a sv_maxupdaterate parancsokkal.
A kliens a perifériákról ugyanazzal a tickrate-tel mintavételezi az adatokat, és generálja ezekbõl a szerver számára feldolgozható parancsokat (user commands), mint amin a szerver is fut. (igen praktikus, hisz így a szervernek csak az általános késéssel kell törõdnie, hisz maguk a parancsok szinkronban lehetnek a szerver szimulációjával (a szerk.meg). Egy ilyen felhasználói parancs (mostantól usercommand) alapvetõen semmi más, mint egy, az adott szívveréshez tartozó pillanatkép a billentyûzet és az egér státuszáról. Ugyanakkor ahelyett hogy minden egyes tickre új csomagokat küldene a kliens a szervernek az éppen aktuális usercommandokkal, a kliens a usercommandokat tartalmazó csomagokat megadott idõközönként közli csak. Mindez azt is jelenti, hogy kettõ vagy több usercommand is érkezhet ugyanazzal a csomaggal a szerverhez. A kliens a küldés gyakoriságának beállítására a cl_cmdrate parancsot használja, ennek a növelésével javítható az interaktivitása a játéknák, érzékenyebben fog reagálni a változásokra, viszont nagyobb kifele menõ sávszélességet követel.
A játék által kommunikált adatmennyiség, ú.n. delta kompressziót, egy adott tömörítési eljárást használ, hogy csökkentse a sávszélesség igénybevételét. Ez azt jelenti, hogy a szerver nem közli a klienssel az összes információt, amit tickenként szimulál, hanem csak azon változásokat (delta snapshot), amik az utolsó megerõsített frissítés után történtek. A megerõsítés úgy történik, hogy minden elküldött és fogadott csomaghoz, ami a kliens és a szerver között áthaladt, egy számot rendelnek, hogy mindkét fél tudja követni és ellenõrizni az adatfolyam kontinuitását, folyamatosságát. (igen, ez alapján tudja a kliens, hogy neki pocket loss-a van. a szerk. meg.) Általában teljes pillanatképet (tehát ami nem delta snapshot), csak a játék kezdetekor küld a szerver, vagy ha a kliens oldalon komoly csomagvesztés jelentkezik. (mikor a kliens már nem képes összerakni a pillanatképet, és egyszerûbb neki egy teljes frissítést kérni, hisz a delta tömörítés eredménye sem lenne nagyon más, mert az utolsó visszajelzett frissítés óta majd minden változott. a szerk. meg.) A kliens manuálisan is kérheti a teljes frissítést a cl_fullupdate parancsal. (valószínûleg a csítek elleni harcban lehet szerepe, de ez a parancs csak sv_cheats 1 szerveroldali beállítással mûködik. (A szerk. meg.)
Interpoláció
A kliens annyi pillanatképet, csomagot kap a szervertõl másodpercenként, amekkora a cl_updaterate paraméterének az értéke. Ha egy adott dolog helyzete a játékosnál, a kliens oldalon csak akkor és ott lenne kiszámolva és kirajzolva, ahol és amikor azt éppen aktuálisan a szerver közölné, a mozgó dolgok, modellek, tárgyak, stb. darabosnak, akadozónak látszanának, és szintén problámat jelentene a megjelenítésben az elveszett, vagy figyelmen kívül hagyott csomagok is. A trükk ahhoz, hogy mindezt elkerüljük az, hogy a renderelést, tehát a megjelenítendõ dolgok kiszámolását nem a legfrissebben megkapott pillanatkép, hanem idõben az ezelõtti valamelyik csomag tartalma szerint keszíttetjük el, ezzel a jelenlegi pozíciókhoz vezetõ animáció folyamatossá tehetõ, hisz kitöltõdik a jelenlegi pozició, és az ezt megelõzõ pozíció közötti út. Ezt nevezik kliens oldali entitás interpolaciónak, kicsit magyarosabban kliens oldali folytonosításnak, diszkrét poziciók közötti kitöltõdésnek. (egyik sem magyaros, de úgy kell elképzelni mintha teniszben csak azt tudnánk hogy hol ért földet a labda, és hogy hol ütöttek bele; csak akkor tudjuk szépen megrajzolni a labda repülésének az ívet, ha nem abból indulunk ki, hogy hol ért földet, hanem hogy hol ütött bele a játékos, majd a földetérés, és az ütés, mely idõben elõbb történt, között megmutatjuk, interpoláljuk, hogy merrõl érkezett a labda a földetérés pontjához (A szerk. meg.). Ezen kitöltõdés ki-, bekapcsolható a cl_interpolate 0/1 változó segítségével. Egy másodpercenként húsz pillanatképet frissítõ kliens esetében, az aktuálisan következõ frissítés körülbelül minden 50. ezredmásodpercben érkezik meg. Az elõzõek alapján, ha az aktuális kép kiszámolásához az 50 ezredmásodperccel azelõtti pillanatképet veszi alapul, akkor a dolgok a megjelenítésben az utoljára megkapott pillanatkép, és az az elõtti között tökéletes kitöltés valósítható meg. A Source motor az entitás interpolációt 100 ezredmásodperces késleltetéssel végzi alapértelmezésben, ami azt jelenti, hogy 20 frissítés eséten, az egyik frissítés elvesztésekor is képes két valós pillanatkép közötti helyzetkitöltés elvégzésére. (ez a cl_interp parancs, mely a 100 ezredmásodpercnél 0.1-et jelent. Mikor az ember ezt az értéket 0-ra rakja, az 1/cl_updaterate íródik be automatikusan, mely pontosan azt jelenti, hogy éppen nem képes a két pozíció közötti kitöltésre, hisz az interpoláció mérteke pont a két megkapott pillanatkép között eltelt idõ (A szerk. meg.). Az alábbi ábrán elmélázva láthatjuk a megjelenítés, frissítés és interpoláció kapcsolatát az idõ függvényében.
A kliens oldalra megérkezõ pillanatkép a 344-es ticknél történt, idõben kifejezve a 10.30 másodpercnél. A kliens oldalon az idõ szalad tovább természetesen, és a szívverésének megfelelõ következõ pillanatban, jelen esetben 10:32 (current time), megjelenít egy képkockát, ami viszont a kliens jelenlegi idõpillanata (client time) mínusz az interpolációs (kitöltés miatt szükséges) késleltetés, azaz 0.1 másodperccel csökkentett, 10:22-kor birtokolt pillanatkép alapján számított pozíció. Tehát a megjelenített animáció alapja példánkban, a 340 és a 342 közötti pillanatkép.
Minekután az interpolációs késés 100 ezredmásodperc, az interpoláció akkor is mûködõképes ha a 342-es pillanatkép valamely oknál fogva elveszett. Ekkor ugyanis az interpoláció még mindig használhatja a 340 és 344-es pillanatképek közötti kitöltést. Ha több mint egy csomag veszik el, a kitöltés megbízhatatlanná válik, hisz nincs elég információ a birtokában hozzá. Ebben az esetben a renderelés extrapolációt használ (cl_extrapolate 1), és megpróbálja egyszerû, a birtokában levõ információk alapján, egyenes arányosságon alapuló mintaillesztéssel betippelni, hogy hol lehetnek a dolgok. Az extrapoláció csak 25 századmásodpercig mûködik ( cl_extrapolate_amount) csomagvesztés esetén, mert ezután a motor úgy ítéli meg, hogy a jóslás hibahatára túl alacsonnyá válik, tehát kevésbé valószínû, realitását vesztettnek tekinti saját extrapolációját, kiegészítését.
Predikció
Tegyük fel, hogy egy játékos 0.1 másodperces késést szenved el a hálozata miatt, és elindul elõre. Az információt, hogy õ elindult, tehát a +FORWARD gomb lett nyomva, egy usercommand formájában elõször tárolja, majd továbbítja a kliens a szervernek. Ekkor a szerver feldolgozza ezt, és a játékos a szerveren szimulált világban megindul elõre. Ekkor a világ státusza megváltozik, és a következõ pillanatkép, amit a többi játékosnak lefrissít, már tartalmazza ezt a megváltozott állapotot. Tehát 0.1 másodperccel azután hogy elindult, mindenki más, köztük õ is, érzékeli hogy elindul, és ez a késés minden jatékos bármely akciójára is igaz, legyen az mozgás, lövés, stb., és csak roszabbá válik nagyobb késés esetén.
A játékos akciója és a szervertõl érkezett válasz késése, mely egyben a vizuális megjelenés késését is jelenti, szokatlan, természetellenes érzést kölcsönöz az egész jatéknak, nehezítve a precíz mozgást és lövést. A kliens oldali predikció (cl_predict 1) segít megszûntetni mindezt, csökkentve a késést, és így a játékos a beavatkozásait sokkal közvetlenebbnek érezheti. Ahelyett hogy arra várna a kliens, hogy a szervertõl megérkezzen a saját poziciójának a frissítése, a kliens önmaga megbecsüli a saját usercommandjának az eredményét. Ehhez a kliens pontosan ugyanazokat a beállításokat használja, amit a szerver, és miután a predikció befejezõdött, a játékos már az új poziciójában fogja látni magát, függetlenül attól hogy a szerver õt még a régi pozíciójában látja.
A kliens 0.1 másodperccel ezután kapja meg a szervertõl azt a pillanatképet, amiben azok a változások vannak benne, amiket õ már végrehajtott, és semmi más dolga nincs, mint összehasonlítani a szerver által megadott változásokat a saját maga által végrehajtottakkal. Amennyiben ezek nem egyeznek, predikciós hiba lépett fel, ami azt mutatja, hogy a kliensnek nem álltak birtokában megfelelõ információk a jatéktérben rajta kívül szereplõ dolgokról, mikor a usercommandot végrehajtotta. Ekkor a kliens felülbírálja magát a szervertõl kapott pillanatkép alapján. Amennyiben a cl_showerror 1 be van kapcsolva, a kliens látja mikor következik be ilyen hiba, ugyanakkor ezen hibák kijavításai a jatékban is észlelhetõek, mert kép ugrásához vezetnek. Ahhoz hogy a látható képen ez kevésbe tükrözõdjön vissza, a predikciós hiba fokozatosan javítódik ki egy adott idõintervallum alatt (cl_smoothtime). A predikciós hiba kijavításának az elmosását szintén ki lehet kapcsolni, a cl_smooth 0 kapcsolóval.
Ez a fajta elõrejelzés vagy predikció csak akkor mûködhet, ha a kliens pontosan tisztában van mindazzokkal az információkkal amivel a szerver is bír. Természetesen ez az esetek töredékében lehet csak így, hisz a szerver többet lát, és több belsõ információval rendelkezik mint a kliens. A kliens csak egy apró szeletét láthatja az egész jatéknak, pont annyit, hogy ideális esetben meg tudja mindazt és csakis azt jeleníteni, ami rá tartozik. Éppen ezért csak azt tudhatja pontosan a kliens, ami õ maga, így a predikció csak és kizárólag a saját mozgás esetében lehet pontos, bármi amibe más, interaktív objektum szerepel, már elvbõl elveszíti azt a lehetõséget, hogy õrá nézve predikciót lehessen alkalmazni.
Lag Kompenzáció
Tegyük fel, hogy egy játékos rálõ a célpontra 10.5-kor. Az információ, hogy játékosunk megnyomta a tûz gombot, egy usercommandba fog megérkezni a szerverre. Miközben a csomag amiben ez a usercommand szerepel halad át a hálózaton, a szerver továbbra is folytatja a játék szimulációját, és a célpont elmozoghat egy másik pozícióba, és ezáltal mire a csomag 10.6-kor megérkezik a szerverre, a szerver nem lát találatot, holott játékosunk pontosan célzott. Ennek a hibának a kiküszöbölésére szolgál az sv_unlag 1 .
A lag kompenzációs rendszer emlékezik minden játékos pozíciójára kb. 1 másodpercig visszamenõleg (allítható az sv_maxunlag változóval). Amennyiben egy usercommand befut a szerverhez, a szerver a feldolgozásakor megbecsüli,hogy az adott parancs mikor keletkezett. Mindez az alábbiak alapján történik:
A parancs kiadásának idõpontja = A szerver aktuális idõpillanata - a kliens késése - a kliens interpolációja
Ebben az esetben a szerver a megadott számítás alapján visszahelyezi a szerveroldalon a játékosokat abba az idõpozicióba, amikor a kliens oldali parancsot kiadták, és ez alapján ellenõrzi, hogy milyen következményei vannak az adott usercommandnak. Miután a usercommand lefutott, a szerver visszahelyezi az eredeti pozíciókba a játékosokat. A szerveroldalon, amennyiben nem dedikált szerverrõl, hanem ú.n. listen szerverrõl van szó, az sv_showimpacts 1 parancs segítségével megjeleníthetõvé tehetõ a különbség a szerver oldali és a kliens oldali hitboxok helyzete között.
Ez a kép egy ilyen szerveren keszült 0.2 másodperces lag beállítással (használva a net_fakelag parancsot), a piros hitbox jelzi a célpont pozícióját a kliens oldalon 0.1 másodperccel ezelõtt. Azóta a célpont folytatta a bal irányú mozgást, de hála a beépített lagnak, ez az információ még "utazik a szerver és a kliens között". Miután a lövésre vonatkozó usercommand megérkezett, a szerver visszahelyezte a hitboxot abba a pozícióba, ahol lennie kéne (kék hitbox). A szerver leköveti a lövést, és nyugtázza a találatot (lásd vér). Az is jól látszik, hogy a kliens és a szerver hitboxok nem fedik egymást tökéletesen, de egész jó közelítés, figyelembe véve hogy a legkisebb eltérések is egy ilyen játékban a gyorsan mozgó modelleknél, centiméteres eltéréseket okozhatnak. A multiplayer játékok nem pixelpontosan jegyzik a találatokat, és a pontosság nagyban függ a tickrate-tõl és a célpont mozgási sebességétõl. Mint azt már említettük, a magasabb tickrate növeli a találatérzékelés pontosságát, de majd minden erõforrás tekintetében többet is követel.
Joggal merül fel a kérdés; Miért olyan komplikált a szerver oldalon a találat észlelése? Sokkal egyszerûbb lenne a szervernek elküldeni a pozíciókat csupán, és minden mást a kliensre bízni, elérve ezzel a pixelpontosságú találatfelismerést. A kliens csak közölné a szerverrel amikor "találat"-ot vitt be, és megmondaná kit és hol ért a találat. Sajnos nem megengedhetõ, hogy ilyen fontos döntés kliens oldalon zajlódjon le, mert nem lehet bízni a kliensben. Még akkor is, ha a kliens "tiszta", és VAC fut rajta, a csomagok akkor is módosíthatóak egy köztes harmadik gép által. Ezen "cheat proxik" képesek lennének "találat"-okat csempészni a rajtuk áthaladó csomagokba, úgy hogy a VAC ezellen semmit nem tudna tenni. (nem mintha amúgy akármit is tenne a VAC. A szerk. meg.)
A hálózati késés és lag kompenzáció könnyen eredményezhet paradox, életszerûtlen szituációkat. Például találatot szenvedhet valaki, aki nem is látja az ellenfelet, mert az már takarásban volt. Ezek a hibák kijavíthatatlanok, mert a csomagok sebessége viszonylag lassú, és soha nem közelítheti meg a fény sebességét, (legalábbis a közeljövõben), és így az élethûség mindenképpen csorbát szenved.
NetGraph
A Source motor rengeteg lehetõséget és eszközt kínál, hogy a hálózati beállítások helyzetét ellenõrizni lehessen. Ezek közül a legnépszerûbb a netgraph, melynek több verziója is elérhetõ a játékban. Mi most a netgraph 2 paranccsal foglalkozunk. A beérkezõ csomagokat balról jobbra futó vonalak mutatják, és a magassága a vonalaknak a csomag méretére utalnak. Amennyiben szünet látszik a vonalak között, az nem értelmezhetõ, vagy elveszett csomagot jelent. Mindezek felett a vonalak színkóddal vannak ellátva, amelyek pedig a csomagot alkotó információ típusára utalnak.
A netgraph alatt az elsõ sorban az fps, (másodpercenként megjelenített képkockák) az átlagos késés, és az updaterate, tehát a frissítés gyakorisága latható, a második sorban az utolsó megérkezett csomag mérete, az átlagos bejövõ sávszélesség, és a másodpercenként átlagosan kapott csomagok száma látható. Míg a harmadikban, a szervernek küldött csomagokra vonatkozó információk láthatóak.
Az értékek helyes beállításáról a Beállítások menüben tájékozódhatsz.