Testing in open.mp
Testiranje u SA:MP nije lako. Uglavnom, to je samo povezivanje sa serverom i postavljanje otisaka svuda. Biblioteke kao što je y_testing čine pisanje jediničnih testova mnogo lakšim, ali samo za prilično "čisti" kod - to jest, kod koji radi na serveru bez mnogo interakcije igrača. Možete testirati iznenađujuće mnogo ovakvog ponašanja, ali ono je i dalje ograničeno i ne pomaže da se suzi problemi kada se pojave.
Pa kako to open.mp rješava? Za početak, kod je otvorenog koda, tako da imate na raspolaganju cijeli niz postojećih alata za otklanjanje grešaka, kao što su GDB i Visual Studio, za prolazak i provjeru koda. Takođe imamo planove za dodavanje sličnih alata za same skripte zalagača.
Ali najveći dodatak je "mock" biblioteka. Vrlo jednostavno, ovo vam omogućava da kreirate lažne igrače koji se ponašaju potpuno kao pravi igrači, ali bez ikakvog povezanog klijenta. Dakle, sve radnje koje na njima izvrši skripta mogu biti pročitane i analizirane drugom skriptom. Uzmimo najjednostavniji mogući primjer za prvu demonstraciju - pokazujemo igraču kontrolnu tačku i potvrđujemo da je poslan. Sa y_testiranjem izvodite radnju i onda morate zapravo pitati igrača da li se to dogodilo, što je sporo, ne ponovljivo i samo zamorno:
PTEST__ SetPlayerCheckpoint(playerid)
{
SetPlayerCheckpoint(playerid, 0.0, 0.0, 4.0, 5.0);
ASK("Vidite li checkpoint u sredini San Andreasa?");
}
Korištenje biblioteke kao što je "Pawn.RakNet" može ukloniti pitanje, automatizirajući provjeru odlaznih paketa, ali i dalje zahtijeva povezanog igrača tako da se ne može automatizirati i ponoviti:
static
gCheckpointPlayer,
Float:gX,
Float:gY,
Float:gZ,
Float:gS;
PTEST__ SetPlayerCheckpoint(playerid)
{
SetPlayerCheckpoint(playerid, 0.0, 0.0, 4.0, 5.0);
ASSERT(gX == 0.0);
ASSERT(gY == 0.0);
ASSERT(gZ == 4.0);
ASSERT(gS == 5.0);
ASSERT(gCheckpointPlayer == playerid);
}
OPacket:107(playerid, BitStream:bs)
{
gCheckpointPlayer = playerid;
BS_IgnoreBits(bs, 8);
BS_ReadFloat(bs, gX);
BS_ReadFloat(bs, gY);
BS_ReadFloat(bs, gZ);
BS_ReadFloat(bs, gS);
return 1;
}
Sa open.mp imamo API sličan onom Pawn.RakNet-a, ali sa lažnim igračima. Dakle, kreirate igrača, bez instance igre, i koristite ga baš kao običan igrač:
TEST__ SetPlayerCheckpoint()
{
new playerid = Mock_CreatePlayer("Mr Mock");
// Clear all existing packets, for ease of searching.
Mock_PurgeSent(playerid);
// Show a checkpoint normally.
SetPlayerCheckpoint(playerid, 0.0, 0.0, 4.0, 5.0);
// Check a "SetCheckpoint" packet was seen.
new MockPacket:packet = Mock_GetPacket(playerid, "SetCheckpoint");
ASSERT(packet);
if (!packet) return;
// Check the packet has 4 components, each 32 bits.
ASSERT(MockPacket_Components(packet) == 4);
ASSERT(MockPacket_Bits(packet) == 4 * 32);
// Check the various components.
new Float:tmp;
ASSERT(MockPacket_ReadFloat(packet, 0, tmp));
ASSERT(tmp == 0.0);
ASSERT(MockPacket_ReadFloat(packet, 1, tmp));
ASSERT(tmp == 0.0);
ASSERT(MockPacket_ReadFloat(packet, 2, tmp));
ASSERT(tmp == 4.0);
ASSERT(MockPacket_ReadFloat(packet, 3, tmp));
ASSERT(tmp == 5.0);
}
Ovaj kod je potpuno ponovljivo testiran, samostalan (bez globalnih varijabli i dodatnih povratnih poziva) i jednostavan za proširenje.
Ali ponekad to nije baš tako jednostavno. Ako kreirate objekat, ne možete samo provjeriti da li je odmah poslan igraču, jer možda nije nigdje blizu njega i stoga im ga ugrađeni streamer još nije poslao. Da bismo ovo riješili, prvo moramo ići na blagu tangentu da objasnimo kako osnovna sinkronizacija funkcionira za stvari poput SetPlayerPos
. Kada postavite poziciju igrača sa SetPlayerPos
server NE ažurira njihovu poziciju interno i za sve ostale igrače odmah. Umjesto toga, komanda SET POSITION
se šalje tom igraču koji se premješta, oni se teleportiraju na novu poziciju, a zatim prijavljuju novu poziciju serveru u svom sljedećem sinhronizacijskom paketu. Razlog zašto je u osnovi kašnjenje - može postojati jedan ili više sinhronizacijskih paketa koji su još uvijek u letu sa starom pozicijom nakon postavljanja nove pozicije. Dakle, šta ovo znači za lažne igrače? To znači da zapravo ne možete postaviti njihovu poziciju, barem ne na normalan način. Ne postoji pravi klijent, tako da ništa ne prima pakete (tj. naredbu set position), i nema ničega što ponovo sinhronizuje te podatke nazad na server.
Možemo generirati bilo koje lažne podatke o sinhronizaciji za server od lažnih igrača, tako da je to jedan od načina da ažuriramo njihovu poziciju, ali može biti malo glomazan za samo jednu uobičajenu operaciju, tako da postoji Mock_SetPlayerPos
za upravo ovaj jedan slučaj upotrebe . Ali ovo još uvijek nije cijela priča, jer nam je potreban streamer da se pokrene i ažurira sa svojom novom pozicijom, što se događa samo povremeno (svakih nekoliko milisekundi, ovisno o stopi tiketa servera). Za ovo imamo još jednu lažnu funkciju - Mock_Tick
, koja preskače vrijeme servera unaprijed za dati broj mikrosekundi (NE milisekundi). Ovo apsolutno ne bi trebalo koristiti na serverima uživo jer može stvoriti razne čudnosti sa tajmerima i drugim vremenski osjetljivim kodom, ali se može koristiti za lažiranje prolaska vremena u testovima. Imajte na umu da Mock_Tick(10000)
neće kasniti 10 ms, umjesto toga će trenutno skočiti vrijeme unaprijed za 10 milisekundi.
TEST__ SetPlayerCheckpoint()
{
new playerid = Mock_CreatePlayer("Mr Mock");
// Clear all existing packets, for ease of searching.
Mock_PurgeSent(playerid);
// Create an object.
CreateObject(1337, 100.0, 100.0, 4.0);
// Fake the mock player's position updating to the server, near the object.
Mock_SetPlayerPos(playerid, 105.0, 105.0, 4.0);
// Now FAKE pass some time, so the streamer can run (50ms should do).
Mock_Tick(50000);
// Provjetiti da li je CreateObject paket vidjen:
new MockPacket:packet = Mock_GetPacket(playerid, "CreateObject");
ASSERT(packet);
if (!packet) return;
// Check the packet has 4 components, each 32 bits.
ASSERT(MockPacket_Components(packet) > 5);
// Provjeriti komponente paketa:
new int;
new Float:tmp;
ASSERT(MockPacket_ReadInt32(packet, 1, int));
ASSERT(int == 1337);
ASSERT(MockPacket_ReadFloat(packet, 2, tmp));
ASSERT(tmp == 100.0);
ASSERT(MockPacket_ReadFloat(packet, 3, tmp));
ASSERT(tmp == 100.0);
ASSERT(MockPacket_ReadFloat(packet, 4, tmp));
ASSERT(tmp == 4.0);
}
Važna napomena o Mock_Tick
. Ako ga nazovete kao, recimo Mock_Tick(100000)
i imate tajmer od 1 ms pokrenut, ovo će trenutno pokrenuti tajmer da se pokrene, ali će tajmer misliti da je dugo propustio svoje vrijeme (100 ms) i da će se postaviti u red čekanja opet. Tokom poziva Mock_Tick
tajmer će se pokrenuti jednom, ali nakon što se trenutni test završi, tajmer će biti stavljen u red još 99 puta, jedan po tiku jer misli da je propustio 100 puta pozivanja.
Kasnije ćemo pokriti neke tehnike za otklanjanje grešaka višeg nivoa tako da možete lakše čitati i pisati lažne pakete, umesto jednu po komponentu kao u tom kodu, ali ovo uvodi osnove na kojima je sve ostalo izgrađeno. Nadamo se da će svima olakšati razvoj i testiranje koda (i osigurati da je server stabilniji).
Sutra ćemo također ići mnogo više na dizajn API-ja za open.mp. Potpuno je kompatibilan sa SA:MP-om, ali to ne znači da nema paralelnog prostora za poboljšanje.