Jump to content

[Tutorial] Greseli frecvente si altele


Banditul

Recommended Posts

De curand am inceput sa deschid cateva gm-uri RPG si de foarte multe ori anumite gm-uri ma fac sa sterg system32 din calculator prin simplu fapt ca comit greseli mari/mici si asta m-a determinat sa fac acest topic

Sa incepem mai intai cu baza de date. Pe foarte multi i-am auzit ca baza lor a depasit 2MiB in marime , ei bine daca structura bazei de date ar fi fost mult mai buna asta nu s-ar fi intamplat nici macar cu 1000 conturi inserate
Ce vreau sa spun cu asta? Ei bine....

Daca vrei sa salvezi spre exemplu valori de 1 sau 2 si care nu vor fi niciodata sub zero si niciodata mai amri de 127/255 se poate folosi cu usurinta tinyint. Ce este tinyint? Este un int care salveaza valori intre -128 si 127 (sau 0-255 daca unsigned)
Ce il face asa special si il recomand? Pentru ca foloseste doar 1byte de memorie si nu 4bytes ca si int normal ( ce mie 1byte ce imi sunt 4bytes , nu? ) , ei bine aceasta diferenta de bytes conteaza prin simplu fapt ca se acumuleaza pentru fiecare rand inserat(cont, masina sau whatever) si se ajunge la 1.5Mib (depinde de cat de mare e tabelul users/players etc) foarte repede
Si nu, valoarea pe care o pui la lenght/values conteaza doar ca afisaj cand vizualizati prin phpmyadmin tabelul. (desigur asta se aplica doar in cazul valorilor de tip integer)
Deci despre valori de nuemre intregi , sfatul meu este sa folositi si restul tipurilor de date , nu doar INT (asta desigur depinde de ce valori vreti sa stocati ). Cu cat e mai mic tipul de data cu atat mai bine pentru baza de date ( https://dev.mysql.com/doc/refman/5.7/en/integer-types.html )

Bun, acum sa vorbim de VARCHAR , am vazut in multe baze de date nsite valori imense pentru lucruri care nu depaseau 24/32 etc caractere. Deci de ce sa sacrifici memoria bazei de date si a HDD/SSD-ului cand poti sa specifici exact marimea valori pe care vrei sa o stochezi in acel VARCHAR? Ca nu cred ca daca modifici un numar , iti cade mana.

Vreti sa stocati data si ora in mysql? Evitati VARCHAR si stringuri/gettime/getdate inutile. Folositi DATETIME sau TIMESTAMP . De ce? De ce nu, e o alternativa mult mai eficienta si are exact acelasi rezultat(daca nu chiar mai bun). Vreti sa extrageti DATETIME/TIMESTAMP din bazade date? Faceti-o ca pe orcare string , nu trebuie sa va murdariti mana si gm-u scriind gettime/getdate , variabile pentru ele , formatare etc. Se poate face foarte simplu folosind CURENT_TIMESTAMP la valaorea default a coloanei din baza de date care contine timpul . Si daca vreti sa updatati data si ora (last seen spre exemplu).
" UPDATE users SET lastseen = CURRENT_TIMESTAMP WHERE ID = 5" , nimic mai simplu.

Si cat despre storage engine evitati pe cat posibil MyISAM sau altele , folositi InnoDB (pentru ca InnoDB e cel mai bun pentur sa-mp )
Si ca exemplu, va ofer baza de date saints v1.3 vs baza de date saints v1.3 facuta de mine folosind ce am spus mai sus (tabelul users ): http://imgur.com/rE8Q8RC (1008 conturi vs 2008 conturi)

Toate cele de mai sus pe langa faptul ca reduc marimea bazei de  date , dar deasemenea fac scrierea/citirea din baza de date putin mai eficeinte.Desigur , ar mai fi de mentionat despre tabele separate pentru anumite sisteme (pentru a incarca tabela users etc) dar este mult de scris (forgein key, design de structura etc)

Cam atat am avut de spus depsre baza de date (ar mai fi cateva mici chestii , dar nu astea sunt indeajuns)
Incat priveste gm-ul , am cateva obiectii si sfaturi (pentru ca si eu la randu meu am facut aceste greseli , fie ele importante sau mai putin importante)

In primul rand, evitati pe cat posibil mysql_query . De ce? Pentru ca poate produce unsync la server foarte rapid (query-uri mari  sau dese). mysql_query va stopa rularea gm-ului pana ce executa query-ul.
Folositi mysql_tquery cat mai mult posibil , pentru ca va lasa serverul sa-si faca treaba cat timp el executa query-ul. Pentru a incarca datele din baza de date(in special la sistemu de logare ) folositi mysql_pquery. De ce? Pentru ca e capabil sa execue query-uri in paralel (in cazul in care mai multi playeri se logheaza in acelasi timp pe server se va dovedi eficient) 

In al doilea rand, am vazut foarte multe query-uri de genul "UPDATE users SET Money = '%d' WHERE id = '%d'" ,  nimic gresit nu? Merge foarte bine. Dar problema e cauza din care merge. In cazul de fata, se trimit 2 stringuri spre baza de date care vor fi stocate in baza de date ca integer. Nu stiu motivul pentru care mySQL permite asta , dar are un sistem de conversie string > integer. Diferenta dintre a trimite un string si un integer pentru a fi stocate intr-o coloana de tip integer e putin neglijabila , dar nu tot timpul. Pentru ca acea conversie poate dura doar cateva nanosecunde sau poate chiar cateva secunde (depinde de sarcina pe serverul de mysql) , asa ca de ce sa lasi serverul de mysql sa lucreze in plus cand poti evita aceasta greseala.

In al treilea rand , am vazut ceva in gm-u saints care m-a socat( probabil sa fie si in alte gm-uri):

for(new turf = 1; turf < sizeof(TurfInfo); turf++)
{
   TurfInfo[turf][zTime] = 0;
   format(str,sizeof(str),"UPDATE `turfs` SET `Time`='0' WHERE `ID`='%d'",turf);
   mysql_query(SQL,str);
}

Aceasta prostie pur si simplu parca mi-a facut un neuron sa explodeze . Sa zicem ca serverul are 100 turfs  = 100 query trimise cu mysql_query. Ce inseamna asta? La ora 23 lag garantat + posibile probleme de sync si pierderi de date
De ce sa trimiti un astfel de query cand poti face asta:

for(new turf = 1; turf < sizeof(TurfInfo); turf++)
{
   TurfInfo[turf][zTime] = 0;
}
mysql_tquery(SQL,"UPDATE Turfs SET Time = 0");

Ambele fac acelasi lucru, dar modul , viteza si eficienta in care o fac le deiferentiaza enorm. 
Deci de ce sa sacrifici serverele (mysql/sa-mp) cand poti pastra lucrurile simple si eficiente.

In al patrulea rand, folosirea GetPlayerName foarte excesiv. Ce vrea usa spun cu asta? Pentru ca peste se fosoeste GetPlayerName cand de fapt se poate folosi o singura data in tot gm-u. Ce vrea usa psun cu asta? Simplu.
Cream o variabila pentru numele jucatorului( cel fara taguri sau alte ccaturi) in PlayerInfo(sau cum o fi) si folosim la OnPlayerConnect:

GetPlayerName(playerid, PlayerInfo[playerid][pNormalName], MAX_PLAYER_NAME);


Si cand vrem sa ne folosim de numele jucatorului , pur si simplu apelam la aceasta variabila. Deci de ce sa scriem 100000000 de GetPlayerName in tot gm cand putem scrie unul singur si ne facem viata mai simpla?

In al cincelea rand, am vazut foarte multe query de genul :

UPDATE `users` SET `Money`='%d' WHERE `name`='%s'


Ce e gresit in asta? Ei bine, este mult mai rapid si eficient sa ne folosim de ID-ul din baza de date , mai ales ca este un index principal ,iar cautarea dupa ID este mult mai rapida decat cautarea dupa nume (string)
Singurele momente in care vrei sa cauti un player in baza de date dupa nume este la conectare (login) si in cazul in care vrei sa te asiguri ca un player exista in baza de date ( referal , ban etc)

In al saelea rand, am vazut pe multi folosind cea mai neeficienta metoda de anti sql injection (ce cu cautarea dupa anumite caractere) . Mi se pare cea mai mare prostie sa faci asta.
De ce? Pentru ca exista mysql_format care are specificatorul %e care semnifica un string caruia ii se face escape. Daca nu vrei sa folosesti %e pentru a trimite stringuri catre baza de date , se poate folosi deasmeana mysql_real_escape_string care este folosit in background-ul lui %e 

In al saptelea rand, textdaw-urile globale NU se fac asa:

for(new i = 0; i < MAX_PLAYERS; i++)
    {
        Speedd[i] = TextDrawCreate(483.600067, 145.848937, " ");
        TextDrawFont(Speedd[i], 1);
        TextDrawLetterSize(Speedd[i], 0.270000, 1.000000);
        TextDrawColor(Speedd[i], -505290241);
        TextDrawSetOutline(Speedd[i], 1);
        TextDrawSetProportional(Speedd[i], 1);
        TextDrawSetShadow(Speedd[i], 1);

        .....
    }


Asta inseamna 650 (50 sloturi) de textdraw-uri gobale deja epuizate cand limita este de 2048 ( 13000 de textdarw-uri pentru 1000 sloturi) . Ce naiba?? Cine a facut asta , a fost cel mai "destept" om.
Daca vreti sa faceti textdraw per player, folositi textdraw-uri per player. Cum?
Creati cate o variabila pentru fiecare player textdraw in PlayerInfo (PlayerText: text1, PlayerText: text2 etc) si puenti ceva asemanator la OnPlayerConnect:

PlayerInfo[playerid][Speedd] = CreatePlayerTextDraw(playerid,483.600067, 145.848937, " ");
    PlayerTextDrawFont(playerid,PlayerInfo[playerid][Speedd], 1);
    PlayerTextDrawLetterSize(playerid,PlayerInfo[playerid][Speedd], 0.270000, 1.000000);
    PlayerTextDrawColor(playerid,PlayerInfo[playerid][Speedd], -505290241);
    PlayerTextDrawSetOutline(playerid,PlayerInfo[playerid][Speedd], 1);
    PlayerTextDrawSetProportional(playerid,PlayerInfo[playerid][Speedd], 1);
    PlayerTextDrawSetShadow(playerid,PlayerInfo[playerid][Speedd], 1);


Pentru fiecare in parte , si va folisiti de variabila respectiva . ( Player textdraw-urile se sterg la deconectarea playerului singure)
Cat inseamna asta pentru limita de textdraw-uri? 0 pentru cele gloable, si posibil 10-15 per player. Nu e mai ok asa? De ce sa ai 650 , 1300 ....13000 de textdraw-uri cand poti sa ai 10-15 per player ( limita per player este de 256 , si nu are legatura cu textdarw-urile gloable)

In al optulea rand, loop-uri de playeri/masini. Multi inca mai scriu loop-uride genul:

for(new i; i < MAX_PLAYERS;i++)



Cea ce nu mai este atat de eficient si bun , avand in vedere ca exista foreach si GetPlayerPoolSize disponibile si mult mai eficiente (GetVehiclePoolSize in cazuk masinilor)

for(new i = 0, j = GetPlayerPoolSize(); i <= j; i++)
for(new i = 1, j = GetVehiclePoolSize(); i <= j; i++)
foreach(new i: Player)


Ce fac GetPlayerPoolSize si GetVehiclePoolSize? Ei bine, returneaza toate sloturile ocupate( sa zicem ca max de sloturi e 50, si ai abia 3 playeri conectati , GetPlayerPoolSize() va intoarce 3 pentru ca sunt numa 3 playeri / 3 sloturi ocupate , acelasi principiu se aplica si la GetVehiclePoolSize())


In al noulea rand, nu vreti ca playerul care se conecteaza sa mosteneasca datele precedentului player care tocmai a iesit? Nu este nevoie sa scrieti PlayerInfo[playerid][VariabilaX] = 0 la OnPlayerDisconect sau Conenct pentru fiecare variabila in parte.
Ce mai eficienta si rapida metoda este aceasta:

static const empty_player[pInfo];
PlayerInfo[playerid] = empty_player;


 Ce face asta? Atribuie 0 pentru fiecare variabila in parte (pentru cele existente in PlayerInfo) , deci de ce sa scrii N randuri cand poti sa scrii numa 2

In al zecelea rand, evitati pe cat posibil array-uri mari (stringuri etc) . De ce sa definesti un string de 500 cells(2000 bytes) si sa folosesti pentru cel mult 60 caractere? E o pierdere de memorie inutila (da stiu nu e semnificativa , bla bla ,dar totu se aduna si pe termen lung nu are rost).

Cam atat, poate ar mai fi lucruri de spus dar pur si simplu nu-mi mai vin in minte. E cam lung ce-i drept dar am incercat sa explic cat de cat..... Sper sa nu ma injurati cand ajunget ila aceste randuri ca e prea mult de citit.
Sper sa fie cat de cat de folos cuiva si sper ca cel macar sfaturile legate de baza de date sa le vad aplicate (pentru ca pe asta se bazeaza RPG , multe query-uri si mult contact cu baza de date si nu vrem sa avem pierderi de date sau delay-uri sau mai stiu eu ce)

Edited by Banditul
  • Upvote 4
Link to comment
Share on other sites

O obiectie legata de repetari (loop-uri).. de ce sa folosesti: for/while/do while/etc. atata timp cat poti folosi foreach aproape in intreg modul de joc (exceptie cand incarci chestii din baza de date, acolo trebuie sa te folosesti si de for la un moment) care e muuult mai optim.

In rest ce sa zic, ai acoperit multe probleme de care foarte putini probabil stiau (spre exemplu eu stiam de faza cu resetatul variabilelor, pe care am gasit-o aici(click) acum mai mult timp, cea cu stocatul username-ului intr-o variabila, si cea cu updatat-ul in db dupa id), dar ai uitat si de greselile minore pe care multi le fac.

Dar sunt si anumite greseli care sa zicem ca nu sunt asa bombe cu ceas, adica sa nu speriam lumea ca trebuie acum sa-si rescrie intreaga baza de date doar pt. ca are anumite greseli care ar putea-o 'ingrasa', totusi bazele de date sunt facute sa lucreze cu muulte date. Dar daca ai in plan sa iti faci un server gen bugged (ca numar de playeri ma refer), va trebui totusi sa-ti optimizezi baza de date, pt. ca totusi vor fi peste 1.000.000 de conturi.

 

Un tutorial foarte bun, respeeect.

 

Retras

Link to comment
Share on other sites

La 7/25/2017 la 12:42, Kingsley a spus:

O obiectie legata de repetari (loop-uri).. de ce sa folosesti: for/while/do while/etc. atata timp cat poti folosi foreach aproape in intreg modul de joc (exceptie cand incarci chestii din baza de date, acolo trebuie sa te folosesti si de for la un moment) care e muuult mai optim.

In rest ce sa zic, ai acoperit multe probleme de care foarte putini probabil stiau (spre exemplu eu stiam de faza cu resetatul variabilelor, pe care am gasit-o aici(click) acum mai mult timp, cea cu stocatul username-ului intr-o variabila, si cea cu updatat-ul in db dupa id), dar ai uitat si de greselile minore pe care multi le fac.

Dar sunt si anumite greseli care sa zicem ca nu sunt asa bombe cu ceas, adica sa nu speriam lumea ca trebuie acum sa-si rescrie intreaga baza de date doar pt. ca are anumite greseli care ar putea-o 'ingrasa', totusi bazele de date sunt facute sa lucreze cu muulte date. Dar daca ai in plan sa iti faci un server gen bugged (ca numar de playeri ma refer), va trebui totusi sa-ti optimizezi baza de date, pt. ca totusi vor fi peste 1.000.000 de conturi.

 

Un tutorial foarte bun, respeeect.

 

Initial voiam sa scriu doar despre partea de mySQL , dar m-a luat valu si am inceput sa scriu mai mult de atat. Si in momentu asta, ma gandesc ca sta e doar jumate din tot intregu . Meritau sa fie mentioante mai multe lucruri, da merge si atat. Mai ales ca sunt 12k caractere

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. For more details you can also review our Terms of Use and Privacy Policy.