Paano namin pinahusay ang mekanika ng mga ballistic na kalkulasyon para sa isang mobile shooter na may network latency compensation algorithm

Paano namin pinahusay ang mekanika ng mga ballistic na kalkulasyon para sa isang mobile shooter na may network latency compensation algorithm

Kumusta, ako si Nikita Brizhak, isang developer ng server mula sa Pixonic. Ngayon gusto kong pag-usapan ang tungkol sa pag-compensate sa lag sa mobile multiplayer.

Maraming mga artikulo ang isinulat tungkol sa kabayaran sa lag ng server, kabilang ang sa Russian. Hindi ito nakakagulat, dahil ang teknolohiyang ito ay aktibong ginagamit sa paglikha ng Multiplayer FPS mula noong huling bahagi ng 90s. Halimbawa, maaalala mo ang QuakeWorld mod, na isa sa mga unang gumamit nito.

Ginagamit din namin ito sa aming mobile multiplayer shooter na Dino Squad.

Sa artikulong ito, ang layunin ko ay hindi ulitin kung ano ang naisulat nang isang libong beses, ngunit upang sabihin kung paano namin ipinatupad ang lag compensation sa aming laro, na isinasaalang-alang ang aming stack ng teknolohiya at mga pangunahing tampok ng gameplay.

Ilang salita tungkol sa ating cortex at teknolohiya.

Ang Dino Squad ay isang network mobile PvP shooter. Kinokontrol ng mga manlalaro ang mga dinosaur na nilagyan ng iba't ibang armas at nakikipaglaban sa isa't isa sa 6v6 na koponan.

Parehong nakabatay sa Unity ang kliyente at ang server. Ang arkitektura ay medyo klasiko para sa mga shooter: ang server ay awtoritaryan, at ang hula ng kliyente ay gumagana sa mga kliyente. Ang simulation ng laro ay isinulat gamit ang in-house na ECS at ginagamit sa parehong server at client.

Kung ito ang unang pagkakataon na narinig mo ang tungkol sa lag compensation, narito ang isang maikling iskursiyon sa isyu.

Sa mga larong Multiplayer FPS, ang laban ay karaniwang ginagaya sa isang malayong server. Ang mga manlalaro ay nagpapadala ng kanilang input (impormasyon tungkol sa mga key na pinindot) sa server, at bilang tugon ang server ay nagpapadala sa kanila ng isang na-update na estado ng laro na isinasaalang-alang ang natanggap na data. Sa scheme ng pakikipag-ugnayan na ito, ang pagkaantala sa pagitan ng pagpindot sa forward key at sa sandaling gumalaw ang character ng player sa screen ay palaging mas malaki kaysa sa ping.

Habang nasa mga lokal na network ang pagkaantala na ito (sikat na tinatawag na input lag) ay maaaring hindi napapansin, kapag nagpe-play sa pamamagitan ng Internet ay lumilikha ito ng pakiramdam ng "pag-slide sa yelo" kapag kinokontrol ang isang character. Ang problemang ito ay dobleng nauugnay para sa mga mobile network, kung saan ang kaso kapag ang ping ng isang player ay 200 ms ay itinuturing pa rin na isang mahusay na koneksyon. Kadalasan ang ping ay maaaring 350, 500, o 1000 ms. Pagkatapos ay halos imposible na maglaro ng mabilis na tagabaril na may input lag.

Ang solusyon sa problemang ito ay ang client-side simulation prediction. Dito inilalapat ng kliyente mismo ang input sa karakter ng manlalaro, nang hindi naghihintay ng tugon mula sa server. At kapag natanggap ang sagot, ikinukumpara lamang nito ang mga resulta at ina-update ang mga posisyon ng mga kalaban. Ang pagkaantala sa pagitan ng pagpindot sa isang key at pagpapakita ng resulta sa screen sa kasong ito ay minimal.

Mahalagang maunawaan ang nuance dito: palaging kumukuha ang kliyente ayon sa huling input nito, at mga kaaway - na may pagkaantala sa network, ayon sa nakaraang estado mula sa data mula sa server. Iyon ay, kapag bumaril sa isang kaaway, nakikita siya ng manlalaro sa nakaraan na may kaugnayan sa kanyang sarili. Higit pa tungkol sa hula ng kliyente nagsulat kami kanina.

Kaya, nalulutas ng hula ng kliyente ang isang problema, ngunit lumilikha ng isa pa: kung ang isang manlalaro ay bumaril sa punto kung saan ang kaaway ay nasa nakaraan, sa server kapag bumaril sa parehong punto, ang kaaway ay maaaring wala na sa lugar na iyon. Sinusubukan ng kabayaran sa lag ng server na lutasin ang problemang ito. Kapag nagpaputok ng sandata, ibinabalik ng server ang estado ng laro na nakita ng manlalaro nang lokal sa oras ng pagbaril, at sinusuri kung talagang natamaan niya ang kalaban. Kung ang sagot ay "oo," ang hit ay binibilang, kahit na ang kaaway ay wala na sa server sa puntong iyon.

Gamit ang kaalamang ito, sinimulan naming ipatupad ang server lag compensation sa Dino Squad. Una sa lahat, kailangan naming maunawaan kung paano ibalik sa server kung ano ang nakita ng kliyente? At ano nga ba ang kailangang ibalik? Sa aming laro, ang mga hit mula sa mga armas at kakayahan ay kinakalkula sa pamamagitan ng mga raycast at overlay - iyon ay, sa pamamagitan ng pakikipag-ugnayan sa mga pisikal na nakabangga ng kalaban. Alinsunod dito, kailangan naming kopyahin ang posisyon ng mga collider na ito, na "nakita" ng player nang lokal, sa server. Noong panahong iyon, ginagamit namin ang bersyon ng Unity 2018.x. Ang physics API doon ay static, ang pisikal na mundo ay umiiral sa isang kopya. Walang paraan upang i-save ang estado nito at pagkatapos ay ibalik ito mula sa kahon. Ano ang gagawin?

Ang solusyon ay nasa ibabaw; ang lahat ng mga elemento nito ay ginamit na namin upang malutas ang iba pang mga problema:

  1. Para sa bawat kliyente, kailangan nating malaman kung anong oras siya nakakita ng mga kalaban noong pinindot niya ang mga susi. Naisulat na namin ang impormasyong ito sa input package at ginamit ito upang ayusin ang hula ng kliyente.
  2. Kailangan nating maimbak ang kasaysayan ng mga estado ng laro. Nasa loob nito na hahawakan natin ang mga posisyon ng ating mga kalaban (at samakatuwid ay ang kanilang mga nakabangga). Mayroon na kaming kasaysayan ng estado sa server, ginamit namin ito sa pagbuo delta. Dahil alam natin ang tamang panahon, madali nating mahahanap ang tamang estado sa kasaysayan.
  3. Ngayong nasa kamay na natin ang estado ng laro mula sa kasaysayan, kailangan nating ma-synchronize ang data ng player sa estado ng pisikal na mundo. Umiiral na mga collider - ilipat, nawawala ang mga - lumikha, hindi kailangan - sirain. Ang lohika na ito ay naisulat na rin at binubuo ng ilang ECS ​​system. Ginamit namin ito upang humawak ng ilang silid ng laro sa isang proseso ng Unity. At dahil ang pisikal na mundo ay isa sa bawat proseso, kailangan itong magamit muli sa pagitan ng mga silid. Bago ang bawat tick ng simulation, "i-reset" namin ang estado ng pisikal na mundo at muling sinimulan ito ng data para sa kasalukuyang kwarto, sinusubukang muling gamitin ang mga object ng Unity game hangga't maaari sa pamamagitan ng matalinong pooling system. Ang natitira na lang ay gamitin ang parehong lohika para sa estado ng laro mula sa nakaraan.

Sa pamamagitan ng pagsasama-sama ng lahat ng elementong ito, nakakuha kami ng "time machine" na maaaring ibalik ang estado ng pisikal na mundo sa tamang sandali. Ang code ay naging simple:

public class TimeMachine : ITimeMachine
{
     //Π˜ΡΡ‚ΠΎΡ€ΠΈΡ ΠΈΠ³Ρ€ΠΎΠ²Ρ‹Ρ… состояний
     private readonly IGameStateHistory _history;

     //Π’Π΅ΠΊΡƒΡ‰Π΅Π΅ ΠΈΠ³Ρ€ΠΎΠ²ΠΎΠ΅ состояниС Π½Π° сСрвСрС
     private readonly ExecutableSystem[] _systems;

     //Набор систСм, Ρ€Π°ΡΡΡ‚Π°Π²Π»ΡΡŽΡ‰ΠΈΡ… ΠΊΠΎΠ»Π»Π°ΠΉΠ΄Π΅Ρ€Ρ‹ Π² физичСском ΠΌΠΈΡ€Π΅ 
     //ΠΏΠΎ Π΄Π°Π½Π½Ρ‹ΠΌ ΠΈΠ· ΠΈΠ³Ρ€ΠΎΠ²ΠΎΠ³ΠΎ состояния
     private readonly GameState _presentState;

     public TimeMachine(IGameStateHistory history, GameState presentState, ExecutableSystem[] timeInitSystems)
     {
         _history = history; 
         _presentState = presentState;
         _systems = timeInitSystems;  
     }

     public GameState TravelToTime(int tick)
     {
         var pastState = tick == _presentState.Time ? _presentState : _history.Get(tick);
         foreach (var system in _systems)
         {
             system.Execute(pastState);
         }
         return pastState;
     }
}

Ang natitira na lang ay upang malaman kung paano gamitin ang makinang ito upang madaling makabawi sa mga kuha at kakayahan.

Sa pinakasimpleng kaso, kapag ang mekanika ay batay sa isang hitcan, ang lahat ay tila malinaw: bago mag-shoot ang manlalaro, kailangan niyang ibalik ang pisikal na mundo sa nais na estado, gumawa ng raycast, bilangin ang hit o miss, at ibalik ang mundo sa orihinal na estado.

Ngunit kakaunti ang mga ganoong mekaniko sa Dino Squad! Karamihan sa mga armas sa laro ay lumilikha ng mga projectiles - mga mahabang buhay na bala na lumilipad para sa ilang simulation ticks (sa ilang mga kaso, dose-dosenang mga ticks). Ano ang gagawin sa kanila, anong oras sila dapat lumipad?

Π’ sinaunang artikulo tungkol sa stack ng Half-Life network, ang mga lalaki mula sa Valve ay nagtanong ng parehong tanong, at ang kanilang sagot ay ito: ang kompensasyon ng projectile lag ay may problema, at mas mahusay na iwasan ito.

Wala kaming opsyong ito: ang mga sandata na nakabatay sa projectile ay isang pangunahing tampok ng disenyo ng laro. Kaya kailangan naming magkaroon ng isang bagay. Pagkatapos ng ilang brainstorming, bumuo kami ng dalawang opsyon na tila gumagana:

1. Itinatali namin ang projectile sa oras ng player na lumikha nito. Bawat tik ng simulation ng server, para sa bawat bala ng bawat manlalaro, ibabalik namin ang pisikal na mundo sa estado ng kliyente at ginagawa ang mga kinakailangang kalkulasyon. Ginawang posible ng diskarteng ito na magkaroon ng distributed load sa server at predictable flight time ng projectiles. Ang pagiging mahuhulaan ay lalong mahalaga para sa amin, dahil mayroon kaming lahat ng mga projectiles, kabilang ang mga projectiles ng kaaway, na hinulaang sa kliyente.

Paano namin pinahusay ang mekanika ng mga ballistic na kalkulasyon para sa isang mobile shooter na may network latency compensation algorithm
Sa larawan, ang manlalaro sa tik 30 ay nagpaputok ng misayl bilang pag-asa: nakikita niya kung saang direksyon tumatakbo ang kaaway at alam ang tinatayang bilis ng misayl. Lokal na nakikita niya na naabot niya ang target sa ika-33 na tik. Salamat sa lag compensation, lalabas din ito sa server

2. Ginagawa namin ang lahat tulad ng sa unang opsyon, ngunit, sa pagbilang ng isang tik ng bullet simulation, hindi kami tumitigil, ngunit patuloy na ginagaya ang paglipad nito sa loob ng parehong tik ng server, sa bawat oras na inilalapit ang oras nito sa server isa-isang tiktikan at i-update ang mga posisyon ng collider. Ginagawa namin ito hanggang sa mangyari ang isa sa dalawang bagay:

  • Nag-expire na ang bala. Ibig sabihin tapos na ang mga kalkulasyon, mabibilang natin ang isang miss o hit. At ito ay sa parehong tik kung saan ang pagbaril ay nagpaputok! Para sa amin ito ay parehong plus at minus. Isang plus - dahil para sa shooting player na ito ay makabuluhang nabawasan ang pagkaantala sa pagitan ng hit at pagbaba sa kalusugan ng kaaway. Ang downside ay ang parehong epekto ay naobserbahan kapag ang mga kalaban ay nagpaputok sa manlalaro: ang kaaway, tila, nagpaputok lamang ng isang mabagal na rocket, at ang pinsala ay binibilang na.
  • Ang bala ay umabot na sa oras ng server. Sa kasong ito, ang simulation nito ay magpapatuloy sa susunod na server tick nang walang anumang lag compensation. Para sa mabagal na projectiles, ito ay maaaring theoretically bawasan ang bilang ng physics rollbacks kumpara sa unang opsyon. Kasabay nito, ang hindi pantay na pag-load sa simulation ay tumaas: ang server ay alinman sa idle, o sa isang server tick ito ay nagkalkula ng isang dosenang simulation ticks para sa ilang mga bala.

Paano namin pinahusay ang mekanika ng mga ballistic na kalkulasyon para sa isang mobile shooter na may network latency compensation algorithm
Ang parehong senaryo tulad ng sa nakaraang larawan, ngunit kinakalkula ayon sa pangalawang pamamaraan. Ang missile ay "nahuli" sa oras ng server sa parehong tik kung kailan nangyari ang pagbaril, at ang hit ay mabibilang na kasing aga ng susunod na tik. Sa ika-31 na tik, sa kasong ito, hindi na inilalapat ang lag compensation

Sa aming pagpapatupad, ang dalawang diskarte na ito ay nagkakaiba sa loob lamang ng ilang linya ng code, kaya ginawa namin ang pareho, at sa mahabang panahon ay umiral ang mga ito nang magkatulad. Depende sa mekanika ng armas at ang bilis ng bala, pumili kami ng isa o ibang opsyon para sa bawat dinosaur. Ang pagbabago dito ay ang hitsura sa laro ng mekanika tulad ng "kung natamaan mo ang kalaban nang maraming beses sa ganoong oras, makakuha ng ganito at ganoong bonus." Anumang mekaniko kung saan ang oras kung saan ang manlalaro ay natamaan ang kaaway ay gumaganap ng isang mahalagang papel ay tumanggi na magtrabaho sa pangalawang diskarte. Kaya napunta kami sa unang opsyon, at nalalapat na ito sa lahat ng armas at lahat ng aktibong kakayahan sa laro.

Hiwalay, ito ay nagkakahalaga ng pagtataas ng isyu ng pagganap. Kung naisip mo na ang lahat ng ito ay magpapabagal sa mga bagay, sagot ko: ito ay. Ang pagkakaisa ay medyo mabagal sa paglipat ng mga collider at pag-on at off ang mga ito. Sa Dino Squad, sa "pinakamasama" na kaso, maaaring mayroong ilang daang projectiles na umiiral nang sabay-sabay sa labanan. Ang paglipat ng mga collider upang mabilang ang bawat projectile nang paisa-isa ay isang hindi abot-kayang luho. Samakatuwid, ito ay ganap na kinakailangan para sa amin upang mabawasan ang bilang ng physics "rollbacks". Upang gawin ito, gumawa kami ng hiwalay na bahagi sa ECS kung saan itinatala namin ang oras ng manlalaro. Idinagdag namin ito sa lahat ng entity na nangangailangan ng lag compensation (mga projectile, kakayahan, atbp.). Bago namin simulan ang pagproseso ng mga naturang entity, pinagsama-sama namin ang mga ito sa oras na ito at pinoproseso ang mga ito nang sama-sama, ibinabalik ang pisikal na mundo nang isang beses para sa bawat cluster.

Sa yugtong ito mayroon kaming pangkalahatang gumaganang sistema. Ang code nito sa medyo pinasimpleng anyo:

public sealed class LagCompensationSystemGroup : ExecutableSystem
{
     //Машина Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ
     private readonly ITimeMachine _timeMachine;

     //Набор систСм лагкомпСнсации
     private readonly LagCompensationSystem[] _systems;
     
     //Наша рСализация кластСризатора
     private readonly TimeTravelMap _travelMap = new TimeTravelMap();

    public LagCompensationSystemGroup(ITimeMachine timeMachine, 
        LagCompensationSystem[] lagCompensationSystems)
     {
         _timeMachine = timeMachine;
         _systems = lagCompensationSystems;
     }

     public override void Execute(GameState gs)
     {
         //На Π²Ρ…ΠΎΠ΄ кластСризатор ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π΅ ΠΈΠ³Ρ€ΠΎΠ²ΠΎΠ΅ состояниС,
         //Π° Π½Π° Π²Ρ‹Ρ…ΠΎΠ΄ Π²Ρ‹Π΄Π°Π΅Ρ‚ Π½Π°Π±ΠΎΡ€ Β«ΠΊΠΎΡ€Π·ΠΈΠ½Β». Π’ ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΊΠΎΡ€Π·ΠΈΠ½Π΅ Π»Π΅ΠΆΠ°Ρ‚ энтити,
         //ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ для лагкомпСнсации Π½ΡƒΠΆΠ½ΠΎ ΠΎΠ΄Π½ΠΎ ΠΈ Ρ‚ΠΎ ΠΆΠ΅ врСмя ΠΈΠ· истории.
         var buckets = _travelMap.RefillBuckets(gs);

         for (int bucketIndex = 0; bucketIndex < buckets.Count; bucketIndex++)
         {
             ProcessBucket(gs, buckets[bucketIndex]);
         }

         //Π’ ΠΊΠΎΠ½Ρ†Π΅ лагкомпСнсации ΠΌΡ‹ восстанавливаСм физичСский ΠΌΠΈΡ€ 
         //Π² исходноС состояниС
         _timeMachine.TravelToTime(gs.Time);
     }

     private void ProcessBucket(GameState presentState, TimeTravelMap.Bucket bucket)
     {
         //ΠžΡ‚ΠΊΠ°Ρ‚Ρ‹Π²Π°Π΅ΠΌ врСмя ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΊΠΎΡ€Π·ΠΈΠ½Ρ‹
         var pastState = _timeMachine.TravelToTime(bucket.Time);

         foreach (var system in _systems)
         {
               system.PastState = pastState;
               system.PresentState = presentState;

               foreach (var entity in bucket)
               {
                   system.Execute(entity);
               }
          }
     }
}

Ang natitira na lang ay i-configure ang mga detalye:

1. Unawain kung magkano ang limitahan ang maximum na distansya ng paggalaw sa oras.

Mahalaga para sa amin na gawing naa-access ang laro hangga't maaari sa mga kondisyon ng mahihirap na mga mobile network, kaya nilimitahan namin ang kuwento na may margin na 30 ticks (na may tick rate na 20 Hz). Nagbibigay-daan ito sa mga manlalaro na matamaan ang mga kalaban kahit na sa napakataas na ping.

2. Tukuyin kung aling mga bagay ang maaaring ilipat sa oras at alin ang hindi.

Kami, siyempre, ay gumagalaw sa aming mga kalaban. Ngunit ang mga na-install na kalasag ng enerhiya, halimbawa, ay hindi. Napagpasyahan namin na mas mabuting bigyan ng priyoridad ang kakayahan sa pagtatanggol, gaya ng kadalasang ginagawa sa mga online shooter. Kung ang manlalaro ay naglagay na ng kalasag sa kasalukuyan, ang lag-compensated na mga bala mula sa nakaraan ay hindi dapat lumipad dito.

3. Magpasya kung kinakailangan upang mabayaran ang mga kakayahan ng mga dinosaur: kagat, buntot, atbp. Napagpasyahan namin kung ano ang kailangan at iproseso ang mga ito ayon sa parehong mga patakaran tulad ng mga bala.

4. Tukuyin kung ano ang gagawin sa mga nakabangga ng manlalaro kung kanino ginagawa ang lag compensation. Sa mabuting paraan, ang kanilang posisyon ay hindi dapat lumipat sa nakaraan: dapat makita ng manlalaro ang kanyang sarili sa parehong oras kung saan siya ay nasa server na ngayon. Gayunpaman, ibinabalik din namin ang mga nakabangga ng shooting player, at may ilang mga dahilan para dito.

Una, pinapabuti nito ang clustering: maaari naming gamitin ang parehong pisikal na estado para sa lahat ng mga manlalaro na may malapit na mga ping.

Pangalawa, sa lahat ng raycasts at overlaps palagi naming ibinubukod ang mga nakabangga ng player na nagmamay-ari ng mga kakayahan o projectiles. Sa Dino Squad, kinokontrol ng mga manlalaro ang mga dinosaur, na medyo hindi karaniwang geometry ayon sa mga pamantayan ng shooter. Kahit na bumaril ang manlalaro sa hindi pangkaraniwang anggulo at ang trajectory ng bala ay dumaan sa dinosaur collider ng manlalaro, hindi ito papansinin ng bala.

Pangatlo, kinakalkula namin ang mga posisyon ng armas ng dinosaur o ang punto ng aplikasyon ng kakayahan gamit ang data mula sa ECS bago pa man magsimula ang lag compensation.

Bilang isang resulta, ang tunay na posisyon ng mga nakabangga ng lag-compensated player ay hindi mahalaga para sa amin, kaya kinuha namin ang isang mas produktibo at sa parehong oras mas simpleng landas.

Hindi basta-basta matatanggal ang latency ng network, maaari lang itong i-mask. Tulad ng anumang iba pang paraan ng pagbabalatkayo, ang kompensasyon sa lag ng server ay may mga kapalit. Pinapabuti nito ang karanasan sa paglalaro ng manlalaro na bumaril sa gastos ng player na binaril. Para sa Dino Squad, gayunpaman, ang pagpili dito ay halata.

Siyempre, ang lahat ng ito ay kailangang bayaran din ng tumaas na pagiging kumplikado ng code ng server sa kabuuan - kapwa para sa mga programmer at mga taga-disenyo ng laro. Kung mas maaga ang simulation ay isang simpleng sunud-sunod na tawag ng mga system, pagkatapos ay may lag compensation, ang mga nested loop at sanga ay lumitaw dito. Gumastos din kami ng maraming pagsisikap para maging kumportable sa trabaho.

Sa 2019 na bersyon (at marahil mas maaga), ang Unity ay nagdagdag ng buong suporta para sa mga independiyenteng pisikal na eksena. Ipinatupad namin ang mga ito sa server halos kaagad pagkatapos ng pag-update, dahil gusto naming mabilis na mapupuksa ang pisikal na mundo na karaniwan sa lahat ng mga silid.

Binigyan namin ang bawat game room ng sarili nitong pisikal na eksena at sa gayon ay inalis ang pangangailangan na "i-clear" ang eksena mula sa data ng kalapit na silid bago kalkulahin ang simulation. Una, nagbigay ito ng makabuluhang pagtaas sa produktibidad. Pangalawa, ginawa nitong posible na mapupuksa ang isang buong klase ng mga bug na lumitaw kung ang programmer ay nagkamali sa code ng paglilinis ng eksena kapag nagdaragdag ng mga bagong elemento ng laro. Ang mga ganitong error ay mahirap i-debug, at madalas na nagresulta ang mga ito sa estado ng mga pisikal na bagay sa eksena ng isang silid na "dumaloy" sa isa pang silid.

Bilang karagdagan, nagsaliksik kami sa kung ang mga pisikal na eksena ay maaaring gamitin upang iimbak ang kasaysayan ng pisikal na mundo. Iyon ay, sa kondisyon, maglaan ng hindi isang eksena sa bawat kuwarto, ngunit 30 eksena, at gumawa ng paikot na buffer mula sa mga ito, kung saan iimbak ang kuwento. Sa pangkalahatan, ang opsyon ay naging gumagana, ngunit hindi namin ito ipinatupad: hindi ito nagpakita ng anumang nakatutuwang pagtaas sa pagiging produktibo, ngunit nangangailangan ng medyo mapanganib na mga pagbabago. Mahirap hulaan kung paano kumilos ang server kapag nagtatrabaho nang mahabang panahon na may napakaraming eksena. Samakatuwid, sinunod namin ang panuntunan: "Kung hindi ito nasira, huwag mo itong ayusin'.

Pinagmulan: www.habr.com

Magdagdag ng komento